From fda64d372c39ebdb94e6ac1f691bb384c8801404 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 01/16] Vendor idealista/clickhouse_role 3.5.2 as ansible/roles/clickhouse (unmodified) Full tree of https://github.com/idealista/clickhouse_role at tag 3.5.2 (5ca82f11c0bd554c4b52045a57cb0e0bc6addb61, the last upstream release), by Idealista S.A.U. and contributors, under the Apache License 2.0, byte for byte. Not used yet: until the next commit, ansible-galaxy keeps installing idealista.clickhouse_role 3.5.1 from its GitHub repository (requirements/ansible-galaxy.yml). --- ansible/roles/clickhouse/.ansible-lint | 5 + ansible/roles/clickhouse/.gitattributes | 3 + .../clickhouse/.github/CODE_OF_CONDUCT.md | 74 + .../roles/clickhouse/.github/CONTRIBUTING.md | 100 + .../clickhouse/.github/ISSUE_TEMPLATE.md | 36 + .../.github/PULL_REQUEST_TEMPLATE.md | 26 + ansible/roles/clickhouse/.gitignore | 361 ++++ ansible/roles/clickhouse/.travis.yml | 19 + ansible/roles/clickhouse/.yamllint | 32 + ansible/roles/clickhouse/CHANGELOG.md | 214 +++ ansible/roles/clickhouse/LICENSE | 67 + ansible/roles/clickhouse/Pipfile | 17 + ansible/roles/clickhouse/Pipfile.lock | 835 +++++++++ ansible/roles/clickhouse/README.md | 192 ++ ansible/roles/clickhouse/defaults/main.yml | 798 ++++++++ ansible/roles/clickhouse/handlers/main.yml | 23 + ansible/roles/clickhouse/logo.gif | Bin 0 -> 24279 bytes ansible/roles/clickhouse/meta/main.yml | 17 + .../clickhouse/molecule/default/Dockerfile.j2 | 18 + .../config/config.d/log_disable.xml | 13 + .../default/config_files/config/config.xml | 1257 +++++++++++++ .../config_files/copier/copier-config.xml | 24 + .../users/users.d/log_queries.xml | 8 + .../default/config_files/users/users.xml | 124 ++ .../clickhouse/molecule/default/converge.yml | 5 + .../files/jdbc-drivers/file.drivers.here | 0 .../default/group_vars/clickhouse_group.yml | 226 +++ .../clickhouse/molecule/default/molecule.yml | 50 + .../molecule/default/tests/test_app.yml | 98 + .../clickhouse/molecule/default/verify.yml | 60 + .../clickhouse/tasks/config/clickhouse.yml | 65 + .../roles/clickhouse/tasks/config/copier.yml | 21 + .../clickhouse/tasks/config/databases.yml | 49 + .../clickhouse/tasks/config/grants-roles.yml | 12 + .../roles/clickhouse/tasks/config/grants.yml | 55 + .../tasks/config/perms_and_privs.yml | 41 + .../roles/clickhouse/tasks/config/quotas.yml | 39 + .../roles/clickhouse/tasks/config/roles.yml | 39 + .../tasks/config/settings-profiles.yml | 39 + .../roles/clickhouse/tasks/config/users.yml | 39 + .../roles/clickhouse/tasks/install-Debian.yml | 108 ++ ansible/roles/clickhouse/tasks/main.yml | 79 + .../clickhouse/tasks/service/clickhouse.yml | 28 + .../tasks/service/jdbc-bridge-Debian.yml | 103 ++ .../clickhouse/templates/DDL/DATABASE.j2 | 5 + .../roles/clickhouse/templates/DDL/GRANT.j2 | 4 + .../clickhouse/templates/DDL/GRANT_ROLE.j2 | 2 + .../roles/clickhouse/templates/DDL/QUOTA.j2 | 7 + .../roles/clickhouse/templates/DDL/REVOKE.j2 | 25 + .../roles/clickhouse/templates/DDL/ROLE.j2 | 5 + .../templates/DDL/SETTINGS-PROFILE.j2 | 4 + .../roles/clickhouse/templates/DDL/USER.j2 | 10 + .../templates/clickhouse-server.service.j2 | 32 + .../roles/clickhouse/templates/config.xml.j2 | 1605 +++++++++++++++++ .../clickhouse/templates/copier-config.xml.j2 | 20 + .../clickhouse-jdbc-bridge.service.j2 | 26 + .../templates/jdbc-bridge/datasource.json.j2 | 6 + .../jdbc-bridge/logging.properties.j2 | 14 + .../templates/jdbc-bridge/set-env.sh.j2 | 5 + .../roles/clickhouse/templates/users.xml.j2 | 190 ++ .../roles/clickhouse/test-requirements.txt | 6 + 61 files changed, 7385 insertions(+) create mode 100644 ansible/roles/clickhouse/.ansible-lint create mode 100644 ansible/roles/clickhouse/.gitattributes create mode 100644 ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md create mode 100644 ansible/roles/clickhouse/.github/CONTRIBUTING.md create mode 100644 ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md create mode 100644 ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 ansible/roles/clickhouse/.gitignore create mode 100644 ansible/roles/clickhouse/.travis.yml create mode 100644 ansible/roles/clickhouse/.yamllint create mode 100644 ansible/roles/clickhouse/CHANGELOG.md create mode 100644 ansible/roles/clickhouse/LICENSE create mode 100644 ansible/roles/clickhouse/Pipfile create mode 100644 ansible/roles/clickhouse/Pipfile.lock create mode 100644 ansible/roles/clickhouse/README.md create mode 100644 ansible/roles/clickhouse/defaults/main.yml create mode 100644 ansible/roles/clickhouse/handlers/main.yml create mode 100644 ansible/roles/clickhouse/logo.gif create mode 100644 ansible/roles/clickhouse/meta/main.yml create mode 100644 ansible/roles/clickhouse/molecule/default/Dockerfile.j2 create mode 100644 ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml create mode 100644 ansible/roles/clickhouse/molecule/default/config_files/config/config.xml create mode 100644 ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml create mode 100644 ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml create mode 100644 ansible/roles/clickhouse/molecule/default/config_files/users/users.xml create mode 100644 ansible/roles/clickhouse/molecule/default/converge.yml create mode 100644 ansible/roles/clickhouse/molecule/default/files/jdbc-drivers/file.drivers.here create mode 100644 ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml create mode 100644 ansible/roles/clickhouse/molecule/default/molecule.yml create mode 100644 ansible/roles/clickhouse/molecule/default/tests/test_app.yml create mode 100644 ansible/roles/clickhouse/molecule/default/verify.yml create mode 100644 ansible/roles/clickhouse/tasks/config/clickhouse.yml create mode 100644 ansible/roles/clickhouse/tasks/config/copier.yml create mode 100644 ansible/roles/clickhouse/tasks/config/databases.yml create mode 100644 ansible/roles/clickhouse/tasks/config/grants-roles.yml create mode 100644 ansible/roles/clickhouse/tasks/config/grants.yml create mode 100644 ansible/roles/clickhouse/tasks/config/perms_and_privs.yml create mode 100644 ansible/roles/clickhouse/tasks/config/quotas.yml create mode 100644 ansible/roles/clickhouse/tasks/config/roles.yml create mode 100644 ansible/roles/clickhouse/tasks/config/settings-profiles.yml create mode 100644 ansible/roles/clickhouse/tasks/config/users.yml create mode 100644 ansible/roles/clickhouse/tasks/install-Debian.yml create mode 100644 ansible/roles/clickhouse/tasks/main.yml create mode 100644 ansible/roles/clickhouse/tasks/service/clickhouse.yml create mode 100644 ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml create mode 100644 ansible/roles/clickhouse/templates/DDL/DATABASE.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/GRANT.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/QUOTA.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/REVOKE.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/ROLE.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/USER.j2 create mode 100644 ansible/roles/clickhouse/templates/clickhouse-server.service.j2 create mode 100644 ansible/roles/clickhouse/templates/config.xml.j2 create mode 100644 ansible/roles/clickhouse/templates/copier-config.xml.j2 create mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 create mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 create mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 create mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 create mode 100644 ansible/roles/clickhouse/templates/users.xml.j2 create mode 100644 ansible/roles/clickhouse/test-requirements.txt diff --git a/ansible/roles/clickhouse/.ansible-lint b/ansible/roles/clickhouse/.ansible-lint new file mode 100644 index 00000000..f8b56157 --- /dev/null +++ b/ansible/roles/clickhouse/.ansible-lint @@ -0,0 +1,5 @@ +exclude_paths: + - ./molecule +parseable: true +use_default_rules: true +verbosity: 1 diff --git a/ansible/roles/clickhouse/.gitattributes b/ansible/roles/clickhouse/.gitattributes new file mode 100644 index 00000000..d08e2bfc --- /dev/null +++ b/ansible/roles/clickhouse/.gitattributes @@ -0,0 +1,3 @@ +*.yml linguist-detectable=true +*.yaml linguist-detectable=true +*.html linguist-detectable=false diff --git a/ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md b/ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..4e06e5dc --- /dev/null +++ b/ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## 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. + +## 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 unacceptable behavior by participants 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 + + +## Our Responsibilities + +Project 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 maintainers 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. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [labs@idealista.com](mailto:labs@idealista.com). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org \ No newline at end of file diff --git a/ansible/roles/clickhouse/.github/CONTRIBUTING.md b/ansible/roles/clickhouse/.github/CONTRIBUTING.md new file mode 100644 index 00000000..bc1f6f3c --- /dev/null +++ b/ansible/roles/clickhouse/.github/CONTRIBUTING.md @@ -0,0 +1,100 @@ +# Contributing to Idealista + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +The following is a set of guidelines for contributing to Idealista's repositories, which are hosted in the [Idealista Organization](https://github.com/idealista) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +#### Table Of Contents + +[Code of Conduct](#code-of-conduct) + +[How Can I Contribute?](#how-can-i-contribute) + * [Reporting Bugs](#reporting-bugs) + * [Suggesting Enhancements](#suggesting-enhancements) + * [Pull Requests](#pull-requests) + * [Changelog](#changelog) + +[Styleguides](#styleguides) + * [Git Commit Messages](#git-commit-messages) + +## Code of Conduct + +This project and everyone participating in it is governed by the [Idealista Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [labs@idealista.com](mailto:labs@idealista.com). + + +## How Can I Contribute? + +### Reporting Bugs + +This section guides you through submitting a bug report for Idealista. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. + +Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](ISSUE_TEMPLATE.md), the information it asks for helps us resolve issues faster. + +> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +#### Before Submitting A Bug Report + +* **Check the last version.** Check if you can reproduce the problem in the latest version of the project. +* **Check the FAQ of the project** for a list of common questions and problems. +* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aidealista)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Bug Report? + +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on the project repository and provide the following information by filling in [the template](ISSUE_TEMPLATE.md). + +Explain the problem and include additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected to see instead and why.** + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for Idealista, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. + +Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](ISSUE_TEMPLATE.md), including the steps that you imagine you would take if the feature you're requesting existed. + +#### Before Submitting An Enhancement Suggestion + +* **Check the last version.** Check if you can reproduce the problem in the latest version of the project. +* **Check the FAQ of the project** for a list of common questions and problems. +* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aidealista)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on the project repository and provide the following information by filling in [the template](ISSUE_TEMPLATE.md): + +* **Use a clear and descriptive title** for the issue to identify the suggestion. +* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. +* **Provide specific examples to demonstrate the steps**. +* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. +* **Explain why this enhancement would be useful**. +* **List some other text editors or applications where this enhancement exists.** +* **Specify which version are you're using.** + +### Pull Requests + +* Fill in [the required template](PULL_REQUEST_TEMPLATE.md) +* Any pull request should have **idealista:develop** as base branch. + +### Changelog + +Every project has a CHANGELOG.md file. Once your code is ready to be merged please fill the issue after the **Unreleased** section as explained: + +* For an enhancement, fill the issue after the **Added** subsection (create it if doesn't exists) +* For a fixed bug, fill the issue after the **Fixed** subsection (create it if doesn't exists) +* For an improvement, fill the issue after the **Changed** subsection (create it if doesn't exists) + +Then write the issue info this way: + +- *[#29](https://github.com/idealista/nginx-role/issues/29) Support debian stretch* @jmonterrubio + +## Styleguides + +### Git Commit Messages + +* Use the present tense ("Add feature" not "Added feature") +* Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +* Limit the first line to 72 characters or less +* Reference issues and pull requests liberally after the first line \ No newline at end of file diff --git a/ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md b/ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..ce3b4bed --- /dev/null +++ b/ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,36 @@ + + +### Prerequisites + +* [ ] Put an X between the brackets on this line if you have done all of the following: + * Checked that your issue isn't already filled: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aidealista + * Checked that there is not already provided the described functionality + +### Description + +[Description of the issue] + +### Steps to Reproduce + +1. [First Step] +2. [Second Step] +3. [and so on...] + +**Expected behavior:** [What you expect to happen] + +**Actual behavior:** [What actually happens] + +**Reproduces how often:** [What percentage of the time does it reproduce?] + +### Versions + +The version/s you notice the behavior. + +### Additional Information + +Any additional information, configuration or data that might be necessary to reproduce the issue. \ No newline at end of file diff --git a/ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md b/ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..2eb9c295 --- /dev/null +++ b/ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +### Requirements + +* Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. +* All new code requires tests to ensure against regressions +* Remember to set **idealista:develop** as base branch; + +### Description of the Change + + + + +### Benefits + + + +### Possible Drawbacks + + + +### Applicable Issues + + \ No newline at end of file diff --git a/ansible/roles/clickhouse/.gitignore b/ansible/roles/clickhouse/.gitignore new file mode 100644 index 00000000..77bb3b65 --- /dev/null +++ b/ansible/roles/clickhouse/.gitignore @@ -0,0 +1,361 @@ +# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig + +# Created by https://www.toptal.com/developers/gitignore/api/ansible,git,intellij+all,pycharm+all,python,virtualenv,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=ansible,git,intellij+all,pycharm+all,python,virtualenv,visualstudiocode + +### Ansible ### +*.retry + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### PyCharm+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### PyCharm+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + + +# Sonarlint plugin + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +### VirtualEnv ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +pip-selfcheck.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope +!.vscode/*.code-snippets + +# End of https://www.toptal.com/developers/gitignore/api/ansible,git,intellij+all,pycharm+all,python,virtualenv,visualstudiocode + +# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) + +tests/playbook.retry +tests/.cache +__pycache__ +.pytest_cache +.molecule +.cache + +*.iml +.idea +.project + +*.pyc +**/.vscode +/logs \ No newline at end of file diff --git a/ansible/roles/clickhouse/.travis.yml b/ansible/roles/clickhouse/.travis.yml new file mode 100644 index 00000000..a5a86edd --- /dev/null +++ b/ansible/roles/clickhouse/.travis.yml @@ -0,0 +1,19 @@ +--- + +dist: focal +language: python +python: "3.11" +os: linux +services: + - docker +install: + - pip install -I pipenv + - pipenv sync +env: + jobs: + - MOLECULE_DISTRO=debian:bullseye-slim +script: + - pipenv run molecule test + +notifications: + webhooks: https://galaxy.ansible.com/api/v1/notifications/ diff --git a/ansible/roles/clickhouse/.yamllint b/ansible/roles/clickhouse/.yamllint new file mode 100644 index 00000000..fb79e4bf --- /dev/null +++ b/ansible/roles/clickhouse/.yamllint @@ -0,0 +1,32 @@ +--- +# Based on ansible-lint config +extends: default + +ignore: | + molecule/**/tests/ + .ansible-lint + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + colons: + max-spaces-after: -1 + level: error + commas: + max-spaces-after: -1 + level: error + comments: disable + empty-lines: + max: 3 + level: error + hyphens: + level: error + key-duplicates: enable + line-length: disable + new-lines: + type: unix + truthy: disable diff --git a/ansible/roles/clickhouse/CHANGELOG.md b/ansible/roles/clickhouse/CHANGELOG.md new file mode 100644 index 00000000..2c037245 --- /dev/null +++ b/ansible/roles/clickhouse/CHANGELOG.md @@ -0,0 +1,214 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a changelog](https://github.com/olivierlacan/keep-a-changelog). + +## [Unreleased](https://github.com/idealista/clickhouse_role/tree/develop) + +## [3.5.1](https://github.com/idealista/clickhouse_role/tree/3.5.1) (2024-04-18) + +### :hammer_and_wrench: Fixed + +- [#73](https://github.com/idealista/clickhouse_role/issues/73) text_log table config crashes Clickhouse + +### :repeat: Updated + +- Tasks now are using FQDN +- Test requirements has been updated + +## [3.5.0](https://github.com/idealista/clickhouse_role/tree/3.5.0) (2024-01-31) + +### :heavy_plus_sign: Added + +- [#70](https://github.com/idealista/clickhouse_role/issues/70) Include extra config and users files +- Update default lts version to latest (23.8.9.54) + +## [3.4.1](https://github.com/idealista/clickhouse_role/tree/3.4.1) (2024-01-26) + +### :hammer_and_wrench: Fixed + +- [#67](https://github.com/idealista/clickhouse_role/issues/67) Fix log tables partitioning + +## [3.4.0](https://github.com/idealista/clickhouse_role/tree/3.4.0) (2023-11-17) + +### :heavy_plus_sign: Added + +- [#64](https://github.com/idealista/clickhouse_role/issues/64) Add clickhouse_keeper and default replica params enhancement + +### :repeat: Updated + +- [#65](https://github.com/idealista/clickhouse_role/issues/65) Update to use ansible_os_family var instead of ansible_distribution var for main.yml + +## [3.3.6](https://github.com/idealista/clickhouse_role/tree/3.3.6) (2023-06-09) + +### :hammer_and_wrench: Fixed + +- [#59](https://github.com/idealista/clickhouse_role/issues/59) Fix role wiping when there are users from readonly storage + +## [3.3.5](https://github.com/idealista/clickhouse_role/tree/3.3.5) (2023-06-09) + +### :heavy_plus_sign: Added + +- [#56](https://github.com/idealista/clickhouse_role/issues/56) Add support for several ldap servers and role mappings + +## [3.3.4](https://github.com/idealista/clickhouse_role/tree/3.3.4) (2023-05-29) + +### :heavy_plus_sign: Added + +- [#53](https://github.com/idealista/clickhouse_role/issues/53) Add LDAP support for authentication. + +## [3.3.3](https://github.com/idealista/clickhouse_role/tree/3.3.3) (2023-03-27) + +### :repeat: Updated + +- [#50](https://github.com/idealista/clickhouse_role/issues/50) [REFACTOR] Adecuate service file changes to default CH version + +### :hammer_and_wrench: Fixed + +- Some typos and quotation + +## [3.3.2](https://github.com/idealista/clickhouse_role/tree/3.3.2) (2023-01-18) + +### :heavy_plus_sign: Added + +- [#44](https://github.com/idealista/clickhouse_role/issues/44) Allow extra packages installation. + +### :hammer_and_wrench: Fixed + +- [#45](https://github.com/idealista/clickhouse_role/issues/45) Ensure existence of base path where clickhouse will be installed + +## [3.3.1](https://github.com/idealista/clickhouse_role/tree/3.3.1) (2023-01-18) + +### :heavy_plus_sign: Added + +- [#41](https://github.com/idealista/clickhouse_role/issues/41) add TTL to system tables and default TTL values. + +## [3.3.0](https://github.com/idealista/clickhouse_role/tree/3.3.0) (2022-11-12) + +### :hammer_and_wrench: Fixed + +- [#36](https://github.com/idealista/clickhouse_role/issues/36) Ansible galaxy meta malformed + +### :heavy_plus_sign: Added + +- [#35](https://github.com/idealista/clickhouse_role/issues/35) Clickhouse copier basic configuration + +### :repeat: Updated + +- [#34](https://github.com/idealista/clickhouse_role/issues/34) The Clickhouse JDBC engine requirements installation + +## [3.2.1](https://github.com/idealista/clickhouse_role/tree/3.2.1) (2022-08-30) + +### :hammer_and_wrench: Fixed + +- [#30](https://github.com/idealista/clickhouse_role/issues/30) Fixed clickhouse server fails after upgrading + +## [3.2.0](https://github.com/idealista/clickhouse_role/tree/3.2.0) (2022-06-01) + +### :heavy_plus_sign: Added + +- [#26](https://github.com/idealista/clickhouse_role/issues/26) New feature to manage GRANT perms and privs +- Corresponding vars to use that feature +- Testing vars for this feature in molecule/group_vars +- Necesary templates + +### :hammer_and_wrench: Fixed + +- Tasks with no_log set to False + +### :repeat: Updated + +- Readme +- Test requirements Ansible version to 5.2.0 +- Task tags +- Clickhouse default version + +## [3.1.1](https://github.com/idealista/clickhouse_role/tree/3.1.1) (2022-04-29) + +### :hammer_and_wrench: Fixed + +- [#23](https://github.com/idealista/clickhouse_role/issues/23) Fixed task trying to remove '_temporary_and_external_tables' database @santi-eidu + +## [3.1.0](https://github.com/idealista/clickhouse_role/tree/3.1.0) (2022-02-14) + +### :heavy_plus_sign: Added + +- Option to use custom config xml files +- [#18](https://github.com/idealista/clickhouse_role/issues/18) Option to create and remove basic users, roles, profiles, quotas, etc @ultraheroe + +### :repeat: Updated + +- Default ClickHouse version. +- Requirements versions. +- Readme instructions. +- Molecule verify update to use checksum instead of the deprecated command. +- Default molecule scenario and tests +- Tasks orders and role files skeleton +- [#20](https://github.com/idealista/clickhouse_role/issues/20) clickhouse_macros var refactor @ultraheroe + +### :hammer_and_wrench: Fixed + +- [#16](https://github.com/idealista/clickhouse_role/issues/16) Fix missing user, password and compression values in remote_servers @ultraheroe +- [#14](https://github.com/idealista/clickhouse_role/issues/14) Wait for service listening @jmonterrubio + +## [3.0.1](https://github.com/idealista/clickhouse_role/tree/3.0.1) (2021-12-14) + +### :hammer_and_wrench: Fixed + +- [#8](https://github.com/idealista/clickhouse_role/issues/8) Remove ansible deprecation warning @Pablohn26 +- [#10](https://github.com/idealista/clickhouse_role/issues/10) Fix paths permissions @ultraheroe +- [#11](https://github.com/idealista/clickhouse_role/issues/10) Allow custom tag values for replicated macro var @ultraheroe + +### :heavy_plus_sign: Added + +- Add .gitattributes. +- Add option to set CH release branch version. + +### :repeat: Changed + +- Update CH default version +- Update .travis python version to 3.9 +- Update test-requirements, ansible, molecule, etc +- Update Readme + +## [3.0.0](https://github.com/idealista/clickhouse_role/tree/3.0.0) (2021-11-23) + +### :hammer_and_wrench: Fixed + +- [#5](https://github.com/idealista/clickhouse_role/issues/5) Defined hkp apt keyserver protocol + +### :heavy_plus_sign: Added + +- [#4](https://github.com/idealista/clickhouse_role/issues/4) Required vars for new config templates + +### :repeat: Changed + +- [#4](https://github.com/idealista/clickhouse_role/issues/4) Fully parametrized config files +- .gitignore rules for various IDEs +- Deleted old stretch distro and add bullseye to .travis.yml +- Update test-requirements versions +- Update default version to latest (at this moment v21.11.2.2) +- Check how to check installed version +- Update molecule config and vars + +## [2.0.0](https://github.com/idealista/clickhouse_role/tree/2.0.0) (2020-11-18) + +- *[#1](https://github.com/idealista/clickhouse_role/issues/1) Fix mismatched var names* @frantsao +- *Fix systemd service config: prevent restarting after unit file changes; fix reloading (needs /bin/kill)* @frantsao +- *Fix default database creation* @frantsao +- *Fix missing listening host templating in config.xml* @frantsao +- *Fix admin user templating* @frantsao + +### Added + +- *Added var in order to control service behaviour in config changes* @frantsao + +### Changed + +- *Renamed vars in order to differentiate from other roles vars (BREAKING)* @frantsao + +## [1.0.0](https://github.com/idealista/clickhouse_role/tree/1.0.0) (2020-11-03) + +### Added + +- *First version* @xtianae7 diff --git a/ansible/roles/clickhouse/LICENSE b/ansible/roles/clickhouse/LICENSE new file mode 100644 index 00000000..f2ecd366 --- /dev/null +++ b/ansible/roles/clickhouse/LICENSE @@ -0,0 +1,67 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + Copyright 2020 Idealista S.A.U. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ansible/roles/clickhouse/Pipfile b/ansible/roles/clickhouse/Pipfile new file mode 100644 index 00000000..d0ceeb23 --- /dev/null +++ b/ansible/roles/clickhouse/Pipfile @@ -0,0 +1,17 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +ansible = "==9.4.0" +molecule = "==24.2.0" +docker = "==7.0.0" +ansible-lint = "==24.2.1" +yamllint = "==1.35.1" +molecule-plugins = {extras = ["docker"], version = "==23.5.3"} + +[dev-packages] + +[requires] +python_version = "3.11" diff --git a/ansible/roles/clickhouse/Pipfile.lock b/ansible/roles/clickhouse/Pipfile.lock new file mode 100644 index 00000000..0b12d764 --- /dev/null +++ b/ansible/roles/clickhouse/Pipfile.lock @@ -0,0 +1,835 @@ +{ + "_meta": { + "hash": { + "sha256": "c6770b0238200be2f18b9f45c0bbd8033c1c45c507bba63a07a277025f63efcd" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "ansible": { + "hashes": [ + "sha256:dd431c63380e18c3faca3288ebde8ce2f4f992363ab558a3c11c8f2032d90867", + "sha256:f1d67a2c21dbed3fee4fe579f750e5d20b5a5f13f4399f256a8a70f0505e62f7" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.4.0" + }, + "ansible-compat": { + "hashes": [ + "sha256:74a91807808a39af48ab6595811b9340d1458db26b138362f48bf39292190705", + "sha256:b3e9f9d7c3a1ce6222de444e9dc6fece7eba70ac64f2a0befdc4e2d542018b4a" + ], + "markers": "python_version >= '3.9'", + "version": "==4.1.11" + }, + "ansible-core": { + "hashes": [ + "sha256:111e55d358c2297ec0ce03ba98e6c5ce95947fdf50d878215eb8c183d0c275e4", + "sha256:f9dea5044a86fd95cc27099f4f5c3ae9beb23acf7c3b6331455726c47825922b" + ], + "markers": "python_version >= '3.10'", + "version": "==2.16.6" + }, + "ansible-lint": { + "hashes": [ + "sha256:c3619deaadf852c353decff7242589bd1339e29271552e4e37bfa8f67db4c56b", + "sha256:d9e11aa06429610f0b461ec5eb9adf7317d4b2abb78475a9a1ec7e8f8e2c1316" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==24.2.1" + }, + "attrs": { + "hashes": [ + "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2.0" + }, + "black": { + "hashes": [ + "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d", + "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd", + "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33", + "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965", + "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070", + "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397", + "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745", + "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1", + "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665", + "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436", + "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb", + "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e", + "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6", + "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702", + "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8", + "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8", + "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3", + "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad", + "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf", + "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e", + "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641", + "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2" + ], + "markers": "python_version >= '3.8'", + "version": "==24.4.0" + }, + "bracex": { + "hashes": [ + "sha256:a27eaf1df42cf561fed58b7a8f3fdf129d1ea16a81e1fadd1d17989bc6384beb", + "sha256:efdc71eff95eaff5e0f8cfebe7d01adf2c8637c8c92edaf63ef348c241a82418" + ], + "markers": "python_version >= '3.8'", + "version": "==2.4" + }, + "certifi": { + "hashes": [ + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.2.2" + }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "click-help-colors": { + "hashes": [ + "sha256:b33c5803eeaeb084393b1ab5899dc5476c7196b87a18713045afe76f840b42db", + "sha256:f4cabe52cf550299b8888f4f2ee4c5f359ac27e33bcfe4d61db47785a5cc936c" + ], + "version": "==0.9.4" + }, + "cryptography": { + "hashes": [ + "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee", + "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576", + "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d", + "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30", + "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413", + "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb", + "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da", + "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4", + "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd", + "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc", + "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8", + "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1", + "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc", + "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e", + "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8", + "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940", + "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400", + "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7", + "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16", + "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278", + "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74", + "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec", + "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1", + "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2", + "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c", + "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922", + "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a", + "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6", + "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1", + "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e", + "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac", + "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7" + ], + "markers": "python_version >= '3.7'", + "version": "==42.0.5" + }, + "distro": { + "hashes": [ + "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", + "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2" + ], + "markers": "python_version >= '3.6'", + "version": "==1.9.0" + }, + "docker": { + "hashes": [ + "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b", + "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==7.0.0" + }, + "enrich": { + "hashes": [ + "sha256:0a2ab0d2931dff8947012602d1234d2a3ee002d9a355b5d70be6bf5466008893", + "sha256:f29b2c8c124b4dbd7c975ab5c3568f6c7a47938ea3b7d2106c8a3bd346545e4f" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.7" + }, + "filelock": { + "hashes": [ + "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f", + "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4" + ], + "markers": "python_version >= '3.8'", + "version": "==3.13.4" + }, + "idna": { + "hashes": [ + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + ], + "markers": "python_version >= '3.5'", + "version": "==3.7" + }, + "jinja2": { + "hashes": [ + "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", + "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.3" + }, + "jsonschema": { + "hashes": [ + "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f", + "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5" + ], + "markers": "python_version >= '3.8'", + "version": "==4.21.1" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", + "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" + ], + "markers": "python_version >= '3.8'", + "version": "==2023.12.1" + }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "markupsafe": { + "hashes": [ + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.5" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "molecule": { + "hashes": [ + "sha256:47c982a7d05db7846da77fe7159deb946f26cafb2e388fc71812be0089a41776", + "sha256:eac7d9bf12d2bf5254e13a46a40357615c32e7003409f305c3940e93b65d7bea" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==24.2.0" + }, + "molecule-plugins": { + "extras": [ + "docker" + ], + "hashes": [ + "sha256:87f8ac8d5e9fe1cbdfb784d92b1fd08e7cb11bf02a9391bb34dcb93fadf7a3fc", + "sha256:a2b1437d532d736e3fbc6db7a69ec533e2334b2115ff9245a0b2772ed9738d23" + ], + "markers": "python_version >= '3.9'", + "version": "==23.5.3" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "packaging": { + "hashes": [ + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" + ], + "markers": "python_version >= '3.7'", + "version": "==24.0" + }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", + "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" + ], + "markers": "python_version >= '3.8'", + "version": "==4.2.0" + }, + "pluggy": { + "hashes": [ + "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", + "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.0" + }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + ], + "markers": "python_version >= '3.8'", + "version": "==2.22" + }, + "pygments": { + "hashes": [ + "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", + "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" + ], + "markers": "python_version >= '3.7'", + "version": "==2.17.2" + }, + "pyyaml": { + "hashes": [ + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0.1" + }, + "referencing": { + "hashes": [ + "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844", + "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.34.0" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "resolvelib": { + "hashes": [ + "sha256:04ce76cbd63fded2078ce224785da6ecd42b9564b1390793f64ddecbe997b309", + "sha256:d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf" + ], + "version": "==1.0.1" + }, + "rich": { + "hashes": [ + "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", + "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==13.7.1" + }, + "rpds-py": { + "hashes": [ + "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f", + "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c", + "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76", + "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e", + "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157", + "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f", + "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5", + "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05", + "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24", + "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1", + "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8", + "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b", + "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb", + "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07", + "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1", + "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6", + "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e", + "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e", + "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1", + "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab", + "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4", + "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17", + "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594", + "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d", + "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d", + "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3", + "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c", + "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66", + "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f", + "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80", + "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33", + "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f", + "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c", + "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022", + "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e", + "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f", + "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da", + "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1", + "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688", + "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795", + "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c", + "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98", + "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1", + "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20", + "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307", + "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4", + "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18", + "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294", + "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66", + "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467", + "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948", + "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e", + "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1", + "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0", + "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7", + "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd", + "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641", + "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d", + "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9", + "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1", + "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da", + "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3", + "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa", + "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7", + "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40", + "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496", + "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124", + "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836", + "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434", + "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984", + "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f", + "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6", + "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e", + "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461", + "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c", + "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432", + "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73", + "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58", + "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88", + "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337", + "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7", + "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863", + "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475", + "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3", + "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51", + "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf", + "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024", + "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40", + "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9", + "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec", + "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb", + "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7", + "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861", + "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880", + "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f", + "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd", + "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca", + "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58", + "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e" + ], + "markers": "python_version >= '3.8'", + "version": "==0.18.0" + }, + "ruamel.yaml": { + "hashes": [ + "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", + "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b" + ], + "markers": "python_version >= '3.7'", + "version": "==0.18.6" + }, + "ruamel.yaml.clib": { + "hashes": [ + "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", + "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", + "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", + "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", + "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", + "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b", + "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", + "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", + "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", + "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", + "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", + "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1", + "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", + "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675", + "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", + "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7", + "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", + "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312", + "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa", + "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", + "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b", + "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6", + "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3", + "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", + "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", + "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3", + "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe", + "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", + "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed", + "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", + "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", + "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", + "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", + "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", + "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", + "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", + "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", + "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", + "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", + "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942", + "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", + "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", + "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", + "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", + "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28", + "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d", + "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", + "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", + "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", + "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" + ], + "markers": "python_version < '3.13' and platform_python_implementation == 'CPython'", + "version": "==0.2.8" + }, + "selinux": { + "hashes": [ + "sha256:2a88b337ac46ad0f06f557b2806c3df62421972f766673dd8bf26732fb75a9ea", + "sha256:ecf7add45c939e9dda682c390a2cd0a845c94a4793a2cce9e8870d4ee9501f99" + ], + "markers": "sys_platform == 'linux'", + "version": "==0.3.0" + }, + "subprocess-tee": { + "hashes": [ + "sha256:b3c124993f8b88d1eb1c2fde0bc2069787eac720ba88771cba17e8c93324825d", + "sha256:eca56973a1c1237093c2055b2731bcaab784683b83f22c76f26e4c5763402e28" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.1" + }, + "urllib3": { + "hashes": [ + "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.1" + }, + "wcmatch": { + "hashes": [ + "sha256:24c19cedc92bc9c9e27f39db4e1824d72f95bd2cea32b254a47a45b1a1b227ed", + "sha256:c0088c7f6426cf6bf27e530e2b7b734031905f7e490475fd83c7c5008ab581b3" + ], + "markers": "python_version >= '3.8'", + "version": "==8.5.1" + }, + "yamllint": { + "hashes": [ + "sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3", + "sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.35.1" + } + }, + "develop": {} +} diff --git a/ansible/roles/clickhouse/README.md b/ansible/roles/clickhouse/README.md new file mode 100644 index 00000000..e6341bc8 --- /dev/null +++ b/ansible/roles/clickhouse/README.md @@ -0,0 +1,192 @@ +# Clickhouse Ansible role + +![GitHub release (latest by date)](https://img.shields.io/github/v/release/idealista/clickhouse_role?color=%23B62682) +[![Build Status](https://travis-ci.org/idealista/clickhouse_role.png)](https://travis-ci.org/idealista/clickhouse_role) [![Ansible Galaxy](https://img.shields.io/badge/galaxy-idealista.clickhouse_role-B62682.svg)](https://galaxy.ansible.com/idealista/clickhouse_role) + +![Logo](https://raw.githubusercontent.com/idealista/clickhouse_role/main/logo.gif) + +This ansible role installs [Clickhouse](https://clickhouse.com/) in a Debian environment. It has been tested for Debian bullseye. + +This role has been generated using the [cookiecutter](https://github.com/cookiecutter/cookiecutter) tool, you can generate a similar role that fits your needs using the this [cookiecutter template](https://github.com/idealista/cookiecutter-ansible-role). + +- [Getting Started](#getting-started-checkered_flag) + - [Prerequisites](#prerequisites-ballot_box_with_check) + - [Installing](#Installing-inbox_tray ) +- [Usage](#usage-runner) +- [Testing](#testing-test_tube) +- [Built With](#built-with-building_construction) +- [Versioning](#versioning-card_file_box) +- [Authors](#authors-superhero) +- [License](#license-spiral_notepad) +- [Contributing](#contributing-construction_worker) + +## Getting Started :checkered_flag: + +These instructions will get you a copy of the role for your Ansible playbook. Once launched, it will install [Clickhouse](https://clickhouse.com/) in a Debian system. + +### Prerequisites :ballot_box_with_check: + +Ansible 5.x.x version installed. + +Molecule 3.x.x version installed. + +For testing purposes, [Molecule](https://molecule.readthedocs.io/) with [Docker](https://www.docker.com/) as driver and [Goss](https://github.com/aelsabbahy/goss) as verifier. + +### Installing :inbox_tray: + +Create or add to your roles dependency file (e.g requirements.yml): + +```yml +- src: idealista.clickhouse_role + scm: git + version: 3.2.0 + name: clickhouse_role +``` + +Install the role with ansible-galaxy command: + +```sh +ansible-galaxy install -p roles -r requirements.yml -f +``` + +Use in a playbook: + +```yml +--- +- hosts: someserver + roles: + - role: clickhouse_role +``` + +## Usage :runner: + +Look to the [defaults](defaults/main.yml) properties file to see the possible configuration properties, it is very likely that you will not need to override any variables but don't forget to set your Admin user 🦸 + +- [`main.yml`](./defaults/main.yml) for superset general purpose vars. + +### 👉 Don't forget + +- 🦸 To set your Admin user and use a secure 🔑 password. +- 📝 To set the `clickhouse_custom_config_file_path` and / or `clickhouse_custom_users_file_path` if you are going to use custom config files. + - 👉 See the [ClickHouse doc](https://clickhouse.com/docs/en/operations/configuration-files/). +- ☑️ To enable or disable using `clickhouse_role_manage_X` vars what things the role should manage. +- 📝 To set users, quotas, profiles, grants, databases to create. + - ℹ️ Or to unset if you want to DROP things. + - 👉 See the default molecule scenario [`group_vars`](./molecule/default/group_vars/clickhouse_group.yml) for more + +### ❗ You must know + +- ❗ To make us of the 'EXCEPT' clauses for [quota assignation](https://clickhouse.com/docs/en/sql-reference/statements/create/quota/) or [user grantees](https://clickhouse.com/docs/en/sql-reference/statements/create/user/#grantees) for example, you can add a minus or dash _( - )_ before the name. +- ❗ When setting `password_type` for users, it should be one of [this](https://clickhouse.com/docs/en/sql-reference/statements/create/user/#identification) +- ❗ When setting `keyed` for quota, it should be one of [this](https://clickhouse.com/docs/en/sql-reference/statements/create/quota/) +- ❗ In case you're using LDAP or Kerberos, set each with their own property `ldap_server` or 'kerberos' so `password_type` is not required +- ⚠️ `clickhouse_replicated_tables_macros` is deprecated, please use `clickhouse_macros` var + +#### Users and roles + +- ⚠️ Note that are two ways to set users for ClickHouse, `users.xml` or via SQL-query, to distinguish both methods note that in this role we use `clickhouse_custom_users_xml` and `clickhouse_custom_users` respectively (SQL recommended). +- ⚠️ When granting, you must know: + - When performing the GRANT actions to maintain the perms & privs clean a "general" REVOKE is performed before GRANTing + - There is an option to disable that before "GRANTS" clean up: `clickhouse_custom_grants_previous_cleanup` + - When granting permissions and privileges the order of the items in definition list takes precedence, is recommended to do this grant from less to the most restrictive. + - 👉 See example below, more at the default molecule scenario [`group_vars`](./molecule/default/group_vars/clickhouse_group.yml) for more. + - Statements Aliases are valid, but not handled at "ansible level" so this results in task making comparisions like `privileges: [DELETE]` vs `system.grants access_type = ALTER DELETE` (from ClickHouse), so we recommend set "un-aliased" perms and privs. + - When performing REVOKE or GRANT if a problem occurs may be result in unexpected / removed perms & privs in the ClickHouse DB ¡¡Be extra careful!! + - You can GRANT a role to a role, or roles to users with `clickhouse_custom_grant_roles`. + +##### Custom user definition example: + + ```yml + clickhouse_custom_users: + - user: + name: "Takumi" + password_type: plaintext_password + password: "AE86" + networks: + - "IP '::/0'" + settings: + - "max_memory_usage = 10000000000" + role: + - projectd_members + - tofu_shop + profile: + - default + grantees: + - projectd_members + quota: "default" + databases: [ProjectD] + # ldap_server: project.d + # kerberos: "" + + clickhouse_custom_grants: + - on: + databases: [Akina] + tables: ["*"] + privileges: [SELECT] + to: [initial_d] + - on: + # cluster: + databases: ["Akina"] + tables: [calendar, records] + # columns: [Notes] + privileges: [ALL] + to: [Takumi, Iketani] + with_grant_option: True + - on: + # cluster: + databases: ["Akina"] + tables: [records] + privileges: [SELECT, UPDATE] + to: [Iketani] + with_grant_option: False + + clickhouse_custom_grant_roles: + - roles: [initial_d] + to: [Takumi, Iketani] + # cluster: +``` + +## Testing :test_tube: + +### Install dependencies + +```sh +pipenv install -r test-requirements.txt +``` + +For more information read the [pipenv docs](ipenv-fork.readthedocs.io/en/latest/). + +### Testing + +```sh +$ pipenv run molecule test +``` + +## Built With :building_construction: + +![Ansible](https://img.shields.io/badge/ansible-4.10.0-green.svg) +![Molecule](https://img.shields.io/badge/molecule-3.5.2-green.svg) +![Docker](https://img.shields.io/badge/docker-5.0.3-blue.svg) +![Goss](https://img.shields.io/badge/goss-0.3.13-green.svg) + +## Versioning :card_file_box: + +For the versions available, see the [tags on this repository](https://github.com/idealista/clickhouse_role/tags). + +Additionaly you can see what change in each version in the [CHANGELOG.md](CHANGELOG.md) file. + +## Authors :superhero: + +- **Idealista** - _Work with_ - [idealista](https://github.com/idealista) + +See also the list of [contributors](https://github.com/idealista/clickhouse_role/contributors) who participated in this project. + +## License :spiral_notepad: + +![Apache 2.0 License](https://img.shields.io/hexpm/l/plug.svg) + +This project is licensed under the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) license - see the [LICENSE](LICENSE) file for details. + +## Contributing :construction_worker: + +Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. diff --git a/ansible/roles/clickhouse/defaults/main.yml b/ansible/roles/clickhouse/defaults/main.yml new file mode 100644 index 00000000..1beeff4b --- /dev/null +++ b/ansible/roles/clickhouse/defaults/main.yml @@ -0,0 +1,798 @@ +--- + +## General +# Version +clickhouse_version: 23.8.9.54 +clickhouse_release_type: lts # Values should be: stable, lts or testing +clickhouse_install_db: True +clickhouse_install_bridge: False + +# Clickhouse role management +clickhouse_role_manage_users: False +clickhouse_role_manage_roles: False +clickhouse_role_manage_databases: False +clickhouse_role_manage_settings_profiles: False +clickhouse_role_manage_quotas: False +clickhouse_role_manage_grants: False + +## Service options +# Documentation +clickhouse_documentation_link: https://clickhouse.tech/docs/en/ +clickhouse_environment_file: /etc/default/clickhouse + +# Set to restart-clickhouse if you need a complete restart of the service i.e. changing listeners config +clickhouse_handler_on_config_change: "Restart-clickhouse" + +# Owner +clickhouse_user: clickhouse +clickhouse_group: clickhouse + +# Maximum number of files limit +clickhouse_max_files: 32768 + +# Start on boot +clickhouse_service_enabled: True +# Current state: started, stopped +clickhouse_service_state: started + +# Logs +# If wanted to output the logs to a file define the following variable +# More information at https://www.freedesktop.org/software/systemd/man/systemd.exec.html#StandardOutput= +#clickhouse_log_file: +clickhouse_log_output: "{% if clickhouse_log_file is defined %} file:{{ clickhouse_log_file }} {% else %} journal {% endif %}" + +clickhouse_private_tmp: true +clickhouse_limit_no_file: 500000 + +# DEBIAN - Clickhouse +clickhouse_prerequisite_repos: + - apt-transport-https + - ca-certificates + - dirmngr +clickhouse_service: "clickhouse-server" +clickhouse_deb_repo: "deb https://packages.clickhouse.com/deb {{ clickhouse_release_type }} main" +clickhouse_deb_keyserver: hkp://keyserver.ubuntu.com:80 +clickhouse_deb_keyid: 8919F6BD2B48D754 +clickhouse_packages: + - procps + - "clickhouse-client={{ clickhouse_version }}" + - "clickhouse-common-static={{ clickhouse_version }}" + - "clickhouse-server={{ clickhouse_version }}" +# clickhouse_packages_extra: +# - clickhouse-common-static-dbg + +# Config templates +clickhouse_service_template_path: clickhouse-server.service.j2 +clickhouse_config_file_name: config.xml +clickhouse_users_file_name: users.xml + +# Directories +clickhouse_base_path: "/var/lib/clickhouse" +clickhouse_base_directory_mode: 0700 +clickhouse_config_directory: "/etc/clickhouse-server" +clickhouse_config_directory_mode: 0700 +clickhouse_config_directory_config_d: "{{ clickhouse_config_directory }}/config.d" +clickhouse_config_directory_users_d: "{{ clickhouse_config_directory }}/users.d" +clickhouse_log_directory: "/var/log/clickhouse-server" +clickhouse_log_directory_mode: 0740 +clickhouse_data_directory: "{{ clickhouse_base_path }}/data" +clickhouse_data_directory_mode: 0700 +clickhouse_tmp_directory: "{{ clickhouse_base_path }}/tmp" +clickhouse_tmp_directory_mode: 0755 +clickhouse_user_files_directory: "{{ clickhouse_base_path }}/user_files" +clickhouse_access_control_directory: "{{ clickhouse_base_path }}/access/" +# clickhouse_custom_config_file_path: "{{ playbook_dir }}/config_files/config" +# clickhouse_custom_users_file_path: "{{ playbook_dir }}/config_files/users" +# clickhouse_custom_extra_config_file_path: "{{ playbook_dir }}/config_files/config/config.d" +# clickhouse_custom_extra_users_file_path: "{{ playbook_dir }}/config_files/users/users.d" + +# Logs +clickhouse_logger: + level: trace + log: "{{ clickhouse_log_directory }}/clickhouse-server.log" + errorlog: "{{ clickhouse_log_directory }}/clickhouse-server.err.log" + size: 1000M + count: 10 + console: autodetection + +# clickhouse_logger_overrides_legacy: > +# +# none +# + +# clickhouse_logger_overrides: > +# +# +# ContextAccess (default) +# none +# +# +# DatabaseOrdinary (test) +# none +# +# + +# CORS +# clickhouse_custom_headers_response: > +# +#
+# Access-Control-Allow-Origin +# * +#
+#
+# Access-Control-Allow-Headers +# origin, x-requested-with +#
+#
+# Access-Control-Allow-Methods +# POST, GET, OPTIONS +#
+#
+# Access-Control-Max-Age +# 86400 +#
+#
+ +# Network +clickhouse_http_port: 8123 +clickhouse_tcp_port: 9000 +clickhouse_mysql_port: 9004 +clickhouse_postgresql_port: 9005 +# You must define the next two vars if you need to enable https/tcp secured +# clickhouse_https_port: 8443 +# clickhouse_tcp_port_secure: 9440 +# clickhouse_tcp_with_proxy_port: 9011 +clickhouse_interserver_http_port: 9009 +# clickhouse_interserver_https_port: 9010 +# clickhouse_interserver_http_host: +# clikhouse_interserver_http_credentials: +# user: interserver +# password: "{{ interserver_http_credentials_password }}" + + +# Configuration +clickhouse_config: + max_connections: 4096 + keep_alive_timeout: 3 + max_concurrent_queries: 100 + max_server_memory_usage: 0 + max_thread_pool_size: 10000 + max_server_memory_usage_to_ram_ratio: 0.9 + total_memory_profiler_step: 4194304 + total_memory_tracker_sample_probability: 0 + uncompressed_cache_size: 8589934592 + mark_cache_size: 5368709120 + # max_open_files: 262144 + mmap_cache_size: 1000 + compiled_expression_cache_size: 134217728 + compiled_expression_cache_elements_size: 10000 + # tmp_policy: tmp + default_profile: default + custom_settings_prefixes: "" + # system_profile: default + # buffer_profile: default + default_database: default + # timezone: + # umask: 027 + mlock_executable: true + remap_executable: true + builtin_dictionaries_reload_interval: 3600 + max_session_timeout: 3600 + default_session_timeout: 60 + # regions_hierarchy_file: /opt/geo/regions_hierarchy.txt + # regions_names_files_path: /opt/geo/ + # top_level_domains_path: /var/lib/clickhouse/top_level_domains/ + # top_level_domains: # Path to the list is under top_level_domains_path + # - domain: + # name: example_name + # path: /path/to/example_name.dat + dictionaries_config: "*_dictionary.xml" + user_defined_executable_functions_config: "*_function.xml" + # max_table_size_to_drop: 0 + # max_partition_size_to_drop: 0 + format_schema_path: /var/lib/clickhouse/format_schemas/ + # disable_internal_dns_cache: 1 + +clickhouse_default_networks: + - "::1" + - "127.0.0.1" + +# clickhouse_listen_hosts: +# - "::1" +# - "127.0.0.1" +# clickhouse_listen_try: 0 +# clickhouse_listen_reuse_port: 0 +# clickhouse_listen_backlog: 4096 +# clickhouse_remote_url_allow_hosts: + +clickhouse_http_server_default_response:
]]> + +# Copier Configuration +clickhouse_copier_config_file_name: copier-config.xml +clickhouse_copier_config_directory: "{{ clickhouse_config_directory }}" +# clickhouse_copier_custom_config_file_path: "{{ playbook_dir }}/config_files/copier" +clickhouse_copier_logger_level: information +clickhouse_copier_logger_log: /var/log/clickhouse-server/clickhouse-copier.log +clickhouse_copier_logger_errorlog: /var/log/clickhouse-server/clickhouse-copier.err.log +clickhouse_copier_logger_size: 1000M +clickhouse_copier_logger_count: 10 + +# GRPC +# clickhouse_grpc_port: 9100 +clickhouse_enable_ssl: false +clickhouse_ssl_cert_file: /path/to/ssl_cert_file +clickhouse_ssl_key_file: /path/to/ssl_key_file +clickhouse_ssl_require_client_auth: false +clickhouse_ssl_ca_cert_file: /path/to/ssl_ca_cert_file +clickhouse_grpc_compression: deflate +clickhouse_grpc_compression_level: medium +clickhouse_max_send_message_size: -1 +clickhouse_max_receive_message_size: -1 +clickhouse_verbose_logs: false + +# SSL +# Server +clickhouse_certificatefile: /etc/clickhouse-server/server.crt +clickhouse_privatekeyfile: /etc/clickhouse-server/server.key +clickhouse_dhparamsfile: /etc/clickhouse-server/dhparam.pem +clickhouse_verificationmode: none +clickhouse_loaddefaultcafile_server: true +clickhouse_cachesessions_server: true +clickhouse_disableprotocols_server: sslv2,sslv3 +clickhouse_preferserverciphers_server: true +# Client +clickhouse_loaddefaultcafile_client: true +clickhouse_cachesessions_client: true +clickhouse_disableprotocols_client: sslv2,sslv3 +clickhouse_preferserverciphers_client: true +clickhouse_invalidcertificatehandler: + name: RejectCertificateHandler # Use for self-signed: AcceptCertificateHandler + +# Specify backend authenticacion: AD / LDAP (By default, AD) +clickhouse_auth_backend: AD + +# LDAP +# clickhouse_ldap: +# - serverame: +# host: +# port: +# bind_dn: +# base_dn: +# scope: +# search_filter: +# verification_cooldown: +# enable_tls: +# tls_minimum_protocol_version: +# tls_require_cert: +# tls_cert_file: +# tls_key_file: +# tls_ca_cert_file: +# tls_ca_cert_dir: +# tls_cipher_suite: + +# clickhouse_user_directories_ldap: +# server: +# roles: [] +# role_mappings: +# - base_dn: +# scope: +# search_filter: +# attribute: +# prefix: + +# Kerberos +# clickhouse_kerberos: +# principal: +# realm: + +# JDBC Bridge +clickhouse_jdbc_bridge_handler_on_config_change: "Restart-clickhouse-JDBC-bridge" +# clickhouse_jdbc_bridge_user: "{{ clickhouse_user }}" +# clickhouse_jdbc_bridge_group: "{{ clickhouse_group }}" +# clickhouse_jdbc_bridge_service: clickhouse-jdbc-bridge +# clickhouse_jdbc_bridge_service_template_path: jdbc-bridge/clickhouse-jdbc-bridge.service.j2 +# clickhouse_jdbc_bridge_service_state: started +# clickhouse_jdbc_bridge_service_enabled: True +# clickhouse_jdbc_bridge_config_path: "/etc/clickhouse-jdbc-bridge" +# clickhouse_jdbc_bridge_playbook_drivers_path: "{{ playbook_dir }}/files/jdbc-drivers" +# clickhouse_jdbc_bridge_drivers_path: "{{ clickhouse_base_path }}/drivers/" +# clickhouse_jdbc_bridge_documentation_link: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/blob/v{{ clickhouse_jdbc_bridge_version }}/README.md" +# clickhouse_jdbc_bridge_log_path: /var/log/clickhouse-jdbc-bridge +# clickhouse_jdbc_bridge_log_file: "{{ clickhouse_jdbc_bridge_log_path }}/clickhouse-jdbc-bridge.log" +# clickhouse_jdbc_bridge_log_level: DEBUG +# clickhouse_jdbc_bridge_env: | +# JVM_ARGS="-Xms1024m -Xmx2048m" +# clickhouse_jdbc_bridge_version: 2.1.0 +# clickhouse_jdbc_bridge_deb_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge_{{ clickhouse_jdbc_bridge_version }}-1_all.deb" +# clickhouse_jdbc_bridge_rpm_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge-{{ clickhouse_jdbc_bridge_version }}-1.noarch.rpm" +# clickhouse_jdbc_bridge: +# host: 127.0.0.1 +# port: 9019 +# clickhouse_jdbc_bridge_datasources_path: "{{ clickhouse_jdbc_bridge_config_path }}/config/datasources" + +# This is just an example other driver may require other settings +# clickhouse_jdbc_bridge_datasources: +# - name: clickhouse_test +# datasource: +# $schema: "../datasource.jschema" +# clickhouse: +# driverUrls: [] +# driverClassName: +# jdbcUrl: +# username: +# password: + +# clickhouse_remote_servers: +# - server: +# servername: +# # secret: +# shards: +# - shard: +# # internal_replication: +# # weight: +# replicas: +# - replica: +# host: +# port: +# # priority: +# # secure: + +clickhouse_remote_servers: + - server: + servername: test_shard_localhost + shards: + - shard: + replicas: + - replica: + host: localhost + port: 9000 + - server: + servername: test_cluster_two_shards_localhost + shards: + - shard: + replicas: + - replica: + host: localhost + port: 9000 + - shard: + replicas: + - replica: + host: localhost + port: 9000 + - server: + servername: test_cluster_two_shards + shards: + - shard: + replicas: + - replica: + host: 127.0.0.1 + port: 9000 + - shard: + replicas: + - replica: + host: 127.0.0.1 + port: 9000 + - server: + servername: test_cluster_two_shards_internal_replication + shards: + - shard: + internal_replication: true + replicas: + - replica: + host: 127.0.0.1 + port: 9000 + - shard: + internal_replication: true + replicas: + - replica: + host: 127.0.0.1 + port: 9000 + - server: + servername: test_shard_localhost_secure + shards: + - shard: + replicas: + - replica: + host: localhost + port: 9440 + secure: 1 + - server: + servername: test_unavailable_shard + shards: + - shard: + replicas: + - replica: + host: localhost + port: 9000 + - shard: + replicas: + - replica: + host: localhost + port: 1 + +# Clickhouse Keeper +# clickhouse_keeper: +# tcp_port: +# log_storage_path: +# snapshot_storage_path: +# coordination_settings: +# operation_timeout_ms: +# session_timeout_ms: +# raft_logs_level: +# keeper_servers: +# - keeper_server: +# server: +# id: +# hostname: +# port: + +# Zookeeper +# clickhouse_zookeeper: +# - node: +# host: +# port: + +# clickhouse_replicated_tables_macros: # Var deprecated use clickhouse_macros +# - server: servername +# macro: | +# 1 +# 1 + +# Macros +# clickhouse_macros: +# - macro: | +# 1 +# 1 +# server: +# - clickhouse1 +# - clickhouse2 +# - macro: | +# default + +# Graphite +# clickhouse_graphite: +# - node: +# host: # required if defined clickhouse_graphite +# port: # required if defined clickhouse_graphite +# timeout: # required if defined clickhouse_graphite +# interval: +# root_path: +# hostname_in_path: +# metrics: +# events: +# events_cumulative: +# asynchronous_metrics: + +# Prometheus +# clickhouse_prometheus: +# endpoint: "/metrics" +# port: 9363 +# metrics: true +# events: true +# asynchronous_metrics: true +# status_info: true + +# Query tables +clickhouse_tables_log: + # Query log (Used only for queries with setting log_queries = 1.) + query_log: + database: system + table: query_log + ttl: event_date + INTERVAL 30 DAY DELETE + partition_by: toYYYYMM(event_date) + # engine: ENGINE = MergeTree PARTITION BY toYYYYMM(event_date) ORDER BY (event_date, event_time) SETTINGS index_granularity = 1024 # Instead of partition_by, you can provide full engine expression + flush_interval_milliseconds: 7500 + # Trace log + trace_log: + database: system + table: trace_log + ttl: event_date + INTERVAL 30 DAY DELETE + partition_by: toYYYYMM(event_date) + flush_interval_milliseconds: 7500 + # Thread log + thread_log: + database: system + table: query_thread_log + ttl: event_date + INTERVAL 30 DAY DELETE + partition_by: toYYYYMM(event_date) + flush_interval_milliseconds: 7500 + # Views log + views_log: + database: system + table: query_views_log + ttl: event_date + INTERVAL 30 DAY DELETE + partition_by: toYYYYMM(event_date) + flush_interval_milliseconds: 7500 + # Part log + part_log: + database: system + table: part_log + ttl: event_date + INTERVAL 30 DAY DELETE + partition_by: toYYYYMM(event_date) + flush_interval_milliseconds: 7500 + # Text log + # Uncomment to write text log into table. Text log contains all information from usual server log but stores it in structured and efficient way. The level of the messages that goes to the table can be limited (), if not specified all messages will go to the table. + # text_log: + # database: system + # table: text_log + # flush_interval_milliseconds: 7500 + # level: + # Metric log + metric_log: + database: system + table: metric_log + ttl: event_date + INTERVAL 30 DAY DELETE + flush_interval_milliseconds: 7500 + collect_interval_milliseconds: 1000 + # Asynchronous metric log: + asynchronous_metric_log: + database: system + table: asynchronous_metric_log + ttl: event_date + INTERVAL 30 DAY DELETE + flush_interval_milliseconds: 7000 + # OpenTelemetry log + opentelemetry_span_log: + engine: > + engine MergeTree + partition by toYYYYMM(finish_date) + order by (finish_date, finish_time_us, trace_id) + database: system + table: opentelemetry_span_log + flush_interval_milliseconds: 7500 + # Crash log + crash_log: + database: system + table: crash_log + ttl: event_date + INTERVAL 30 DAY DELETE + partition_by: + flush_interval_milliseconds: 1000 + # Session log + session_log: + database: system + table: session_log + ttl: event_date + INTERVAL 30 DAY DELETE + partition_by: toYYYYMM(event_date) + flush_interval_milliseconds: 7500 + +# Compression +# clickhouse_compression: +# - case: +# min_part_size: +# min_part_size_ratio: +# method: +# level: + +# Encryption +# clickhouse_encryption: +# encryption: +# current_key_id: +# nonce: +# elements: +# - key: +# type: # key or keyhex +# id: +# # Key from a value / file or from ENV, use only one option. +# value: +# # from_env: ENV + +# Default replica params +# clickhouse_default_replica_path: +# clickhouse_default_replica_name: + +# Distributed DDL +# clickhouse_distributed_ddl: +# path: +# profile: +# pool_size: +# task_max_lifetime: +# cleanup_delay_period: +# max_tasks_in_queue: + +# MergeTree settings. See See documentation in clickhouse source code, in MergeTreeSettings.h +# clickhouse_mergetree_settings: +# min_compress_block_size: +# max_compress_block_size: +# index_granularity: + +clickhouse_graphitemergetree_settings: + name: graphite_rollup_example + path_column_name: Path + time_column_name: Time + value_column_name: Value + version_column_name: Version + pattern: + regexp: click_cost + function: any + retention: + - age: 0 + precision: 3600 + - age: 86400 + precision: 60 + default: + function: max + retention: + - age: 0 + precision: 60 + - age: 3600 + precision: 300 + - age: 86400 + precision: 3600 + +clickhouse_query_masking_rules: + - rule: + name: hide encrypt/decrypt arguments + regexp: ((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\) # or more secure, but also more invasive: (aes_\w+)\s*\(.*\) + replace: \1(???) + +# clickhouse_http_handlers: +# - rule: +# url: / +# methods: POST,GET +# headers: no-cache +# handler: +# type: dynamic_query_handler +# query_param_name: query +# - rule: +# url: /predefined_query +# methods: POST,GET +# handler: +# type: predefined_query_handler +# query: SELECT * FROM system.settings +# - rule: +# handler: +# type: static +# status: 200 +# content_type: text/plain; charset=UTF-8 +# response_content: config://http_server_default_response + +clickhouse_send_crash_reports: + enabled: false + anonymize: false + endpoint: https://6f33034cfe684dd7a3ab9875e57b1c8d@o388870.ingest.sentry.io/5226277 + +# clickhouse_rocksdb: +# db_options: +# max_background_jobs: 8 +# cf_options: +# num_levels: 2 +# table_options: +# - table: +# name: TABLE +# options: +# max_background_jobs: 8 +# cf_options: +# num_levels: 2 + +# For additional config, for example: when you need to define catboost library paths +# clickhouse_config_xml_extra_config: | +# /home/catboost/data/libcatboostmodel.so +# /home/catboost/models/*_model.xml + +# users.xml + +# Profiles +clickhouse_default_profiles: + # Default settings + default: + max_memory_usage: 10000000000 + use_uncompressed_cache: 0 + load_balancing: random + max_partitions_per_insert_block: 100 + # Profile that allows only read queries + readonly: + readonly: 1 + +# clickhouse_custom_profiles: [] + +# Users +clickhouse_default_users: + - user: + name: default + password: + networks: "{{ clickhouse_default_networks }}" + profile: default + quota: default + - user: + name: readonly + password: readonly + networks: "{{ clickhouse_default_networks }}" + profile: readonly + quota: default + +# clickhouse_admin_users: +# - user: +# name: "{{ clickhouse_admin_user }}" +# password: "{{ clickhouse_admin_password }}" +# networks: "{{ clickhouse_default_networks }}" +# profile: "default" +# quota: "default" +# access_management: 1 +# named_collection_control: 1 +# show_named_collections: 1 +# show_named_collections_secrets: 1 + +# Users examples +# clickhouse_admin_user: admin +# clickhouse_admin_password: admin + +## See templates/users.xml.j2 for other possible passwords format (plain text is not recommended). + +# clickhouse_custom_users_xml: +# - user: +# name: "{{ clickhouse_admin_user }}" +# password: "{{ clickhouse_admin_password }}" +# networks: "{{ clickhouse_default_networks }}" +# profile: "default" +# quota: "default" +# access_management: 1 + +# - user: +# name: "user1" +# password: "user1" +# networks: "{{ clickhouse_default_networks }}" +# profile: "default" +# quota: "default" +# databases: [test1] + +clickhouse_custom_users: [] +# clickhouse_custom_users: +# - user: +# name: "user2" +# password_type: PLAINTEXT_PASSWORD # one of: NO_PASSWORD, PLAINTEXT_PASSWORD, SHA256_PASSWORD, DOUBLE_SHA1_PASSWORD, LDAP, KERBEROS, SHA256_HASH, DOUBLE_SHA1_HASH +# password: "user2" +# networks: "{{ clickhouse_default_networks }}" +# profile: "default" +# quota: "default" +# databases: [test2] + +# Roles +clickhouse_custom_roles: [] + +# Grants +clickhouse_custom_grants_previous_cleanup: True +# clickhouse_custom_grants: +# - on: +# cluster: +# databases: [] +# tables: [] +# columns: [] +# privileges: [] +# to: [] +# clickhouse_custom_grant_roles: +# - roles: [] +# to: [] + +# Quotas +clickhouse_default_quotas: + - quota: + name: default + duration: 3600 + queries: 0 + errors: 0 + result_rows: 0 + read_rows: 0 + execution_time: 0gv + +# clickhouse_custom_quotas_xml: [] +clickhouse_custom_quotas: [] + +# Settings profiles +clickhouse_custom_settings_profiles: [] + +# Databases examples +clickhouse_custom_databases: [] +# clickhouse_custom_databases: +# - name: test1 +# - name: test2 +# - name: test3 + +# clickhouse_remote_servers: "fclickhouse_default_database" + +# For additional config, like clickhouse_config_xml_extra_config but in users.xml +# clickhouse_user_xml_extra_config: | +# value +# +# +# value +# +# diff --git a/ansible/roles/clickhouse/handlers/main.yml b/ansible/roles/clickhouse/handlers/main.yml new file mode 100644 index 00000000..01e0ab18 --- /dev/null +++ b/ansible/roles/clickhouse/handlers/main.yml @@ -0,0 +1,23 @@ +--- + +- name: Restart-clickhouse + systemd: + name: "{{ clickhouse_service }}" + state: restarted + +- name: Reload-clickhouse + systemd: + name: "{{ clickhouse_service }}" + state: reloaded + +- name: Restart-clickhouse-JDBC-bridge + systemd: + name: "{{ clickhouse_jdbc_bridge_service }}" + state: restarted + when: clickhouse_jdbc_bridge is defined + +- name: Reload-clickhouse-JDBC-bridge + systemd: + name: "{{ clickhouse_jdbc_bridge_service }}" + state: reloaded + when: clickhouse_jdbc_bridge is defined diff --git a/ansible/roles/clickhouse/logo.gif b/ansible/roles/clickhouse/logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..d96f592f538fc16bd38c56d49773ce3c6580ddb5 GIT binary patch literal 24279 zcmbTdXH*nH*Dl(Vhde`)%#eqik?ar!1SDs0h>~+O;E=P72!awEqJV%z1w|Qh#vw-$ z0TBch1SJ?azTY|Dx%bCe=dN|@M|Xv1?`Q9-y;pVjs=jQfudL!K1BQZ*0pO{lS?wtH z@2_9?Y28Za3tC%Se$VdQ?$`=XID<>b$&sS`|pe}DPb?};Di@BL3H z$LyI6z~G~lni*i?b5QX@FAj%Wdr`9_ns+^`2nK_$8(V)v_n8)vb z)-3|BZ-$&aUn+Yo{3g|Gr2A58v2a`VonL>C+P6wu-vi6C4~-qrQv%sZH>2$B?F-~R z-_N>{NTlPrzTNdZ-~S8@)EJQap1k+~yz{v8VCL(|r`18Oi5ICFa=-33tO7D&w0At_br3v9}r{_&8fPetg^x@V1 zPr`PdK4jDDom+sFCrDmS?(hDI^R3jQg3&LF3+Fxa=YzvPD<}mEKkg2^ZJWdVro5<~ z2cFSKo*f_8ZuISvtMd!v?>fdz9*O$cnBMeuUR5jEK73Vw*~!vS@8@WTzS#oSZS)0KeN>^2b8@94o90-_}$aTPR{z)0}I=Zhn}5`w`v(0Zw<_r@wkl6 zPrkZvs|gh@Au1*&#;d5J-thrA8f!TmZ0Onn&U@AmN1DUst?u2+K5v?zot-@%YcUj8 z5f&CaTc2TMV&&rCkd~A@9BKM7(sVT3csSDdW2EtDxar4m6N{1_h(4Srii6B(0W9Zzo`gBziqmN!hT+;8}~t9bBV z(!{7ot40R|2Y80NVxj~51H)9KHTYdUg4{e+|JDD~EzOVlFG#qb2EW#S1~C8O)Cmgp z#3)M1NV>}?$zYUZrDWw5E?&GSfsvDum64W_la`f}l)b1bFQ+Oa^DhPetMLD03-$0) zwb0f7UtIrM8vNej;lZlX(os=SQc?0!L7~^BWmQyE{-Z-qPVyf@GAt%A+%;Mk6-_V4hy&N{C~{&Kcd5|VuC%TEj+`5B0}9g|G@?RTlnAH{ojfH1N>)=>eW!6 ze~aSkuN&kZ5#SjZZm6rl|8GRf!^cBaK|x7IMOIJtqK=N1o}#R*va+m-u7Z|=yrR6k zqP~Luf9v=^$;!$rYHR7p>*{D-)RmRhQ`FLz*VB`cl~YiX)t1w{sPf;khJj(>u7U2J z|IO?3kN1Dc{$u^W$g1judb)-Og<1s#`TutWn0W_<2ZebD1!Ht96*0CxfgV9oVgFg2 z|LLo)XQ)r4r-y!MPypt?W>?kc|G=)G`2Xeg|CIIk|1)FK|BR9TPiXwVLgl}^{>9&a zs{cFi|26)1>UakJ%a_o9i2*wQ`{(zsv(uB~pFfTczaQ*>`}$>X_w&y7r>&2h8|xp| z-mkt}SzcOPn4g=SnVy=Yyqy>y8yy)Q8XV~F>+R_#cfEPt`RZjy`-|sotu4(>jn5kD z>z>xuR6nVztSB!lB|R=FE-HNVups|IUhe&zd&KO!S(zE>X{jm6Nr?&ZafCauw{P9N z5fdF184(^98WJ277~t>c>*Iah%hSW%%@u#m#o5Wx!QRf+#@fo#!rbhtsfqCwBSVAB z`g*!L+FH0vni}eAsw&DCl@t}^<^Cn5l%#~Xm?&06SV&NSpAW;!!_CFX!OnJpm4%s! z5shM?r=z8zra~g%Fen5J0sy{KDDa;m@t@0oc3c2?4xCeQ88ni*6Oc?|PD72Qy(uUG zqauT*vi=M3@bDEHR#QD_==6%YG=Z;AHtv=Ce(j>F0NJ$Kb>UPw?9|ptZS6oBd(Ll+)s-rtdh6TgRQ>80QfqymY99zlTtd-} zzJ-Z7hU}v_pzO;Rf=tD@#?tlLBV(t49L7~g&*ZmfH_6&a`iqw@o zt!=lNudnL1n)EbJu@=;CPW_vb+UC6ylNBbpn7($e@i0NEw%{z%I-iAi8yd&;f9>@W zx05j6o=yYuvVT;t?h}tFdM0jk1dbL&nj2H3fHR33e>qdBUR-zsiCYgsW|l6* zN68_mN1~MgU6kNLp^;!Y?`{Wv?|e01t6=CY=C9h^Z~fFRRvwQ#rmfegVa+|uKY|E| z5gk!lY_C*oCd!d5m5ynil@p1fQs2wMobjb9Gy@5Gt%h@U*t&?1Qc=hb_lctcm87+M zi8d<}tP5-X+Whq>H>X~@n}^`d#Uc0~nM6*QBEUZ&D;!2!!MtnGuko`9b7N-n(h1fm z`S)b@V_N!c+F>Ii`(3JZL3g3t#5m4#4xrLO|L*!E|o8iN7*Pz15C*kkP3n2ZZR;ERg~5f;qb|`x6i#UvMzG zB79Z(t$X_S-0{v&a<_k;8z-#EXaC6^1xrvEXq*mtdF^nb&s9_dyf?fVOxA?ou#e6( zt-;{lx|aaiVk0~m7&aJSIJ*a>3H_MApZQ5f%WkCD8l3@+5!JZdaTOiQ_*hQE1lryx zAf=1LyrSV2A0>@T$L^I5V-i{Q@yMW}?6M4xI4Ky0Fb4voE*9Knj@^gce{&D)7ku5w zGsn=&#XL>Hq)hWk!PS;p;$7pJ_a^tfA0eYjmz{tK*2K?x0nxpj0__*N_S@1z_Sb4v z$#l}4K(f&SnGqz5DuJ#6AxEP5qHbozF3 z<9)w~UG-0@<3;B=aB$R@Nv8HjTbr|46t-Zl?K=?1p{@l`vn7IY1O^y04*V`g3&r(F zLEB@r*<*8rE*3!2TJ->SAB}|CX_l1JBE^mj`ZLaQ9nedb_3~{2jvF+#OCynQo;lM7 z-mVY;rMeb+whs>Tn>jJdB6v@>aiHMoIQr`2G9{AKRKv|hPSP}7!XO@y83Xf1+NOTe zNatkk1Iskpmh@TGUxB=II2)MjKQ=Ki$w!W}y@i3Vu1ROd;tjf%AT%U904iYyaZ!lw z;6Bk0*hh16<2IO&Lj1w}e$H|D`7)=NZiWOhSm!9AR|4CcpM*>Vjcs6$_I-t@_o-&e z+RyvHCe>&8?7d}w`@&9|)iukS^8()|yFoQZ0*KZpmBo%gQ%BJXV6IDmC6FM`(@YU0 z+0>iu@!}R_z$07#!(OB^J9w?RAc1HtD(E^EkoFFs7_S=@l>zD~y}~6wW0k{)6HyIe zCA@B*TE2G3LSU`g9J1c*zU8!9!TJKIy0xHif(oU|BfX`& z#6`r5PIiiWQEoQOF~WRmMF%3$4p1W-3cRy)na`P$j_Ew#*su8A>OGmWO#`%&zLfLo z{v@m-wNd(U4AGa)IE@1vTKF_Lm-e+mRY*G969VjJK=(3AqL=+%l<8LvQiaF4&9T|` zq;J$={347%?tH)ZO`v4<&D5{9hH)cXmoDWldn%>wQ%8R&br1YX&iF-YGEM454pq82 zP|TasgvA*gor}g7Tmx6u>}xXo)7O2P3QUszncO>F*W&|P%8Kv>5*kK4&aawxE1d6q zuJN{Qb(cr4_)q6IJNE6zpn8alckbmrrN`GEhC^%@2rB!g&ETAsql_rhPS!U0!%YzU zVmzM?gdHedND;JVN@7ql(w_cx@DsL?ppy4cZT$MHZlQJcuCpx>N6vKzvZLO9Px4LH z&+i*affP&G@`PbJt>1i)l>_j4xmRXkD&m1IW_El37g&x0Hd z`2XxfL@vg;Dkq2PK;K_Pd9#Vh$|ybnr{H86fHE> zNY=4#*o(|~aYY>rID{(QL#CNu{)M(^hnvh7<`fkWBp)SGgG_jRpo5mX-Gsi=X$J5R zGhPk}a}WvCHNaJ}#2Z$OnFVHD-i<_AF2Lo^uVQKAn&8bvFrnGpoEb1pTq(M;r z0N4T#5l->PR|`eSB`3Ja4B}{XDPFth3wMz%_Ze`YR&u%~pD4T~n;Dhx-ZK-f#B<>x zY;-OKAu8AC*_Y=awS@KICNLlv;)WF2exD*Ig)TJM} z*K&wd+W@^8%vvVTDux|?in2G1)?LUOSwOO>_&?Z}J|aG`u0^Q{t*4Gu@u6zi8E zXikgQ*vrHSRGT#q^`hCSTx}0(c#+7*;-5i?eiT+kV^JN>rl```-l7-RbnzF z*TnMP5z3&YW#XzlR7h~8VX)jW>M;P1o=X0mR$d7%dM_s~OL^Rs0QWMe(DxPf^fHta z)4Gmhl-WX2U@s-kFfc#j9G?VPOtCom3Z{|3o(`}UOqH82?50^2ZCV-H4B}TS zFuFi@p`#2ALl2A6H9sns2rC3e%3Ft#{_ZaM!CvvI)wvec=mRRz9ajFh?2LGkGptm8 ze8Cb7DG?vngcfuvjd&;~&_w`#lYyK2r6lTFK?m4Y4bh%VUGq|aWsz>?v`TdgbZ-Y` zOw4Y(QHU&;NnoVQl81{Q-q${RN`8s$IYS+p)k!phZHLhi_q*ZPUA=r+~l?|w6Z=jv=tv=0i0Ci_*fOga#O5s-8a3MDspLRY8X~sn9eiA%Q;0q^ zt9UjFroGwDFvIC_0ZLMABfjD@YHS+q0qh;=DqbQAIm_tDgvXrT7(!ynuE5oE02H^3 zy4wA`?_v6sS)uQO^7XQ3?skS(FgK!?TiOdzzJn~o__*foaAj|5m;?IpK5d3vr_+Zw zhRa1F?Lz63o!n)JN4SpVm-Y7IuM3_MttoBqePM6gU#sUdoOWOP;D>HFQBq!d8KzBM ziU_nIspA0PK&>lR&}L)5Yv!93^M6M#|ZK7A12K|NssL4K_D)>sOAt?L`agZjL*qMGIW_GzVQ zdzHm|sk=cw3N9Ud5$Q^ttagA?V=1~RpXdwVZOGyYFOy@Qh56MRp8I3E_R0JG4CA=k zTneq5QL*iw0QFj?(_TNz0mykR87kiy>?7bH44BiX`2kRGOqYy05SoeV!Zlu`Apdm7 z+J5Gs-lxNmhw0+rZ)#Lq1cg+r;2T4YkG1=T(=TL<@flI5n7FzrT;n}*bS!(t{GWlO z=wY=SSkR!r*IIPO=UxjjSo}UDVTJY8%$04Wi+~KUmHMLLmKSv#R2DCgUK8VL7|~JG zI}U=0KSencNCN_{&czSwF-g_U|`={e- zV6fB@!&MaMhYFgP0Dop6bMkP)KwFDvfmK%JqOA<@=KS>8rJ7LW0ae<~T)I$RiYsR; zTNo{xa{N+NLKGMdu5svTq`T7h3rlyAYLcT2VTsXrt3LS~_u9g+nP*(UNI3DU!<6ti zG&B9#$?)xqc;xm>no-t>r_D5(!$$cw>K5kW=fP=e+-Qsm&;Aq6Y<+4s4TiUCy%Xv7 zmpn$Um#GhqiQhh>{Vp+!U<3m8$5^u3SieZo%FsMqqzhb{)y%QmIU45)q1uX`mkx%I zwsrpW+s_!mZsd$8(DjDWQHF1+Kk;hbD4l0H3v4Q-sj^1N?9*Q46uGW$fsLATN4$z} zE}|nNac1en+ZYr4;)>;>ssG}`fZ>vQcSsG$?L14))1()^4*}zm1*}ldHRp@_QvuzL zL6XZ2MW~Eg5na(rEucQ<4D;nW=jg1r1wSnN7sJWW%XqmL>oW|)#Fdsr^*lT>nKVOH zPbvGpGMR;u*(cA_b#s_bQi|Hf8@rcH?!kxz>&1s;@n@GLtkv8A?nvvWhxRh{BAzxC zA>8kgrAj(fFDz<3)ye=>{HT&0khRPTiG$k;(}gXsWnqP`c|(TH;1=;9CziJREm#Qf zj>QFIvkZoNfXV~kEg;Q20UkH|rLGH|`3RKQqN(A{FEj7^gdPWvK zHXF)5Yg7t-pL5ol7|n?Ryt4f^2hTG)RRjT0Jcz;pO}D3zB3DLki`Eu>M3zB~SLE@l zeYuV>UgND~Wzd=rtl~yhtUub>Y|c2t++El!ppS)^sQ-noqR#(SoINThr#00vw_-1w z<0BXA0%s7K5-LDv@!2lj9-ftGnoQc`!Fr=6z%6MlwCOwWDd;+7r{K$uT?Un-#AmfB zI#=J%7KZQ|P4r_khOMF2JdBt3<~E$XR`k)JL6YuU){B@})UasmfM4ITP)(;?p=@f*)5TA3zB4v`UcWwPC}*+D zXR)32{txiM4Lg5WcnEERwqgcMB*o>$E4hV{T9-CwZ@?ZTy}ZF+$s%YCCw1EY)S+7C zG#-!7-|w>J<-M@_(WT^+_dRW?)aQ%F!!F`K@i)KjtfFLO3W9gPJ`px_LPI%V$YJtH z!2^*KO`w;q&NGt6!L>Eed&uYtHujrcwlqiLGfHz#yM|yIqwWL`_Bg&L9_nxkUx(-g zFaA9oyCbtzMj!R){O0R5m9t9UbvmsX^O07q#!Y*hD~@GyO3%J&`lV-R4^@ImdMirU zF%}7BY;^`XmH{OZDEP}Y0hY{5kTGzbBvQjtTM16_2XEK)GT9XaHy0*1E))6m?h4vZ zZ5rl^S~qyG%bBC$zumsyx4$oJ0+$c@UT9PK{whf;iC#$63P`mV%a!t6%H63GW2!LS z*4Ljh5Vpk_W$0JB>d?Y-I!_$tBxuAYE9%(+T@Ky$kdG7GGI**1DZe$N!=+%_6?X^33@m-b>+IQN%S^`P(lY`OMTN})K%2*r1_pNuU3R)?X*>U2FTi%YG)zNNBnh%L_z9T5>S*G^XQ4GVrQ#_@3f zN;x&LHeEwb;l2(-rLSIY_EtxtDw8?aRl{};ZvvY2-Hpk9w)4|O-S!)qrh3$4YFC@N z@7y|<9YV}7y_FCR#E1_IA-r>pjOAy;ElstIpmF?5-a17^{^_D3A=M9IP#H&9Eg>+y zkPI@O=%Y~E50jnr;zqQcK)pj7k@-V#FGIBZM*ctQ#xfU;s!r|bz4%#ln2}=>yscap`*BQcs z^YV-xla|Zd1MeEse~3yE{245a~9h)fS*3r{|NBYOX7qdOs;#(s@2)yF5SE z5(zvgdO^=sZFnV?^gwk^tZw_3XhJ18eQoEQ%GWu^))%!`{%90)lW1^$&`|&a zHMckIVjD2+mtQ@Ahh@ITRK>h~S1v}~8vk)x~V&K*ATULmr1C_nGX zuNh30F6$Q?R~cNP4;!XLR^mE662_5aps@}b z{x|39?Uz`1=KghDzzxgCl9*5!ixH?um^0Z6opABtz^6{TBtQ7&FqR+PECyB*`k9BR zKOv&r5D{$SZxUL)uab{HsW*0ET0%F`(Rc&g`;v?CiJzzVliI;)QZ$}d&yFZx9K+u_iNXx7MG@BRe#IcfcN@`?~{E~$fp*K zAPms0qlE;T>F({(k7~m_Pp4i?tebk*-+TwW&TGBe255+0C|_Mu~3I;MaW) z2|#O&CCD$plCv19*ozwvR?Hk(tqCu0oT3x7ewwcPC(koV$&4MILl7Lcn3Il>Xmld% zXBw(!cdKn37-C2YQ;&WMbQ$O}d6*@d(po=@JkRq;=rhl<-z$CY{`pJzr&mU=hY;qU znuWqf@c?Zj8J;Br>{pWqI7UCRudE>yb1Cl1gY1Izv5m01tU1?Ca8&yG%?~CrT$a{; zwEeD5rmj6elx*@d>PhV9Sr6x(F6`?{8-QbJDM&5cpuOOnh-506uGlzEA@uJeLKm09 z zKP1GwyTVlFK#XB$Ho5fcp~y&zIAeNYVyUVQ}1FVY-1FLSh0egO@I8Nj{ zm*YnS|LEJm?BYp}8Y|zf1Hx$y!W(_wkc~=G_HbVq7&zXDq_*X2*}QNA35#wK+`b`h zzmfk7BJA%b!P*aUq<2{ui`Aa0-Zppk#W zNC=I!jD1}$B_af^;PfiEP2N|N0L%w+Rhz8lD0|4#I68e$8_P*9_f(eG=WARMNtamk zi0Kem_c zZ_8{)_m`tdekXtR;ExQ(c)^m(S$0|UoEy7r{C!TVe%UXdKUPY*Hl$(1Ka?!;wIOO@ z@Xgw{R2}*?bqYcU$H>2S``hjF^P6kGVg-zFV zWu-|!ri@}1gKGf@i8O`<0?bK^Rh=x20r8Y~=}|6m)O9oMLsji$Rs{wS6 z&^dDgFv>h=+k|p_cZ8NoTi9L4VC@3zCi>S*H-nYvirx~qb#HC-V29|fCIy>xmtQIFd z6lb={Y(^k_X(rIg=-nkI3P(#iRtWmP>b1?)qwmBCGwar5^i(f1nyaNb7wKN3l~mn_ zx@BH2{S|**f{GE)dHoA~#Xlbz-5$Rl533$9RPWC^fk79@*M0Q_iuyx6hJrU`MAZi! zJ7q)?4Pw0^=e%Z@U1qu>e@nzrv}0(8<3`~RNC{ZtYrXxhNc)%Z@q{}l1ARdQC|{4Z z@g;}W;Uq4Fq}E>ROq_Zq?oyEfgGZ;XU|NJn-(9Dt(klk{axY_?(p1d!7kfcD9{Iqg zDB+f&-w~3xApVlEf#0rS&`L=W_DYMwC@w=!Xys{(`sj7-D`6hmSsAZ#lt*NZM+(mf zn%+ilXqDZqs8oqYzspBV(+E`Y;`uWpanQ5|<|{Og+Lxf0m$50K7{q9&@mQbn$QUls z|H|7U!HJczF)ovF+E$$ky28Ys0%F{aBjhV)ole?nhSY>Nb>sFKU^ZPU#`XkBbx__+ zu4ugYxfDjdfJ^7$9#VCWEL5J{cV#?jGi5|DB@~4SBf^Z^C+vqNb~{a3h_JANWFHh_ zQqbgk>)7}n&;D=7?#hIH0ra>iY4^zVpw&33)c`&TMRTOhuk?-N>yLiUxg}7tOZcn zhfz;cK@CVUn;Dxc)d{NiXAa-n2R*9mEhM~JztZ!)cFdy&s~A??10uHks@Q)4sOJEMC95PS90U|m3Hh;dj!TTKuVg0WP5ox&2Gh$bgTq*~iM zBO;K==xC^ubKK2^Sw%fdS=;o_qN8W+U~tbYghPL_>Zq58V;}AozFrYJB(M?kUhGlatCRNAc(`7R4D9RFP*SzBwNGc`-&^lz4)X60% zm~yEatnfMojf8vQ;lTg`pPCZ=@1l22vb=l;rv(zxHGY8Th$-TER0P6-c5$y@NVvrF`xi$L0Y5f>r|HdHboqk?1_JxzzAEYl$ae$T-|>an0N zSHdLIURU&~4Vj3RPCe+9&%n=f_?r<=2*8QeYq5CUlN6ehDd9r1htPZ4kmZa8RV(mNoiq;y2>Y2wZhFm8m%$rX7>TxQh9OH-z z5n^M+N2{nmxq=%hrbr;8-65&WDN_K!5*&XsI*|Z_w^*l~cbF`P0WSR!%YBN=5q8UA z-YJn-m+mCTeiO$i)M_(qr6pkXdBEx?6UR2al@EE#VR%G1a^W(OFjT+T=HIN;?o>WA z*XeGh%ds@UGOUMvcYlA;75eTzDo$nl+WDVr06qTv4-8`|{MBNq((~PYO2Yn2LPqs` zs+DQzwnL*Q9FKB}^j?iXzVDz*4);d1ML0&7IiXCQKEqr#^biNmE3NPF75V(~L=Qzp)1dC|}L{veQ=J?DiK+jok|4$8|NdQROMi0l1ut^v6E z*Hxy-T~XuOYoIX+^hY1PLT#Fcj@5j&o?f7bytdB5hFtq`{r9a zsWQjrgp{fn2$w}zhm{S|0)+;Yy-kS36v@p*S`3!@23TqVSyid^#5ql2LI;jTs8`3| zR7QB2t#orcMR=#6aR^H`1P=-#O9A8A;uw=%RiswXtXmylR(*L~zGy7#dO7avA=JOZ zEKv!bJSi?@_aHCF!92i-G!~%HRrF(X?9USi9#8$gI7j+4OCJ3j3ZBP8%5K1hSbBSA z^{3yXpK_4|UxCY={0Ji=!4nVHG)0_81|wc1c}T8-N>Kn0CH@Y#YvckRb$6rtqswP! z#|+KY&ytR7o-VG*vmyIAB=5wK>LmAu4{Z@nDg{Z8+7{v6He;_x?=XTypI|WA z1z4~yOWL10tuL_{u=A8&Di}W+r-FU1V63B{+sksPO~s(iKQ#f{*hLZ;%SC>4?F0SN zOZ!&UtxNEvUI#-pgCz9m*rJ^SptUsh37$X-dSKO$2NXg6@^~$dMS0qs0wS%&;YmTP zbMDIY!5Ss5eXzsx&yt(Jqyj)}P)+W{KVK1+?LKR_KNztFBC{y+HtR7if@u6#;^r&O z0!XrD8Us$Jco%Z@za@v-6JFmwZZ!v_Fxa@T!*}Uyg3||f^@$0N-aj0P5Ge#T zHvso%+xcLP2sVQtMbk)>Pae2H! zSK=rm?R3r(xvcmtMPCMFgE%2vfHM(y9ut4GvaE)iC0;H_+UfRbyg~6y z$^0c|yQo8{ZPP)|k40%CMM-D^WFya!?2L#-r1P>x*QVSg@J0%*eSdj5x&@XR`Dvz@ zGj#&r9k(-4uNEEnp(lP@-y2N{N|IsvbEc@xPYW2hiYo=q$hl#(Qi8t@^ACO#8Y_?~ z{QcDDPFg!!yBtC5PdGuXW1sXgVTZrL{p6JEWh$dkt>wiY=F8az(Nw5}t?!bqrocU} zsPBBLlj%IJ{PXkDYX!ISGT+%Bi<@^&J-M2F_*->BleaqTn@jiv{~oR_N?LsNF~gOh z`IZ#G_v>kO)a&<&lZIQ@3gSh_@l3|tY7o9;I<73DN4$(drW`e6L6U-n!0RGrE>%%( zL5JX{pcf9lJ63G8N*hPLG#tDtd|C+wR0+v!f@yJCs!uAbhU8k>k1mADrZM5dy!sJV z;YU=Wf>hsrZ?nYch>1GWe<;oujM_?jl8o51U$lW5PDTit9nzg<(B2YCIe(0|KDscb z3S#ZE(-TzTqb&k%CE5pu9ti+kT~xWKq|faMOWr)H;q+=3l5J3_lJss5i^5cq1Zlwo zf{E&GMY)%Zzl?lZh;-KQ{FQ*jDS`Y;unxgm_u)gU8f~qIVs%$0LlBW79xcUD7H%o= zdbVc{IuG{SmhR_k$5Nox?pt>BQeJXQV~-JeRrE>$13$6F?C*Rmf`Nu<%A-3jk0?00 zn1u;99(rA$jB@<1Z4XY9+ON2;&~hYa(R#t&m38J)L``oAhj_Qa4_SXR^EC&VUi~ym zO14Xk+#vC#%aaGs-Z<=+H1@KJS1_()Kh?x0jGK+PLTg^p>%FEXp0B^^@DU^!Zo)5A zr&MZG$aRRaIH1&a@Y7#k(GM)Y?ewD$4-!_oODkq9XTTWz6ms#FB2_E@-ZYxu=}&|y zFXf=03JrfmsLyhS)JVBfr#BXu{|8DBV-6bZLu$p8Kch2S4-qlRP(Jw)T}(+ z{WQ~hzrovIGTg#J`7v7HJm$`4)ur%0_p5Z7^G!BF-^6HtV<17HpnE^EdE+0#;^IN~ zW@Mn8W6@*!G8PI%Wq0}c0?G@e&5pM?L4{biEUjB7k79uy0s0ZAc>Dsx`&6%x?t*S= z2W{2g;ciwJK_3uV>s)c7L_F_FJm^moQZ>9O)3%UgYqpn|J|`lZejOiU=}5u#NKNc57?9xRzU=(|~vaDGQF#04!yzKo-9&c7qE$&+Um z@7hpSpn%oC1@#{I8Z4A~+fG=)J!zl@pP3c-aQVQYjoKPY$d1z`(Du|&nG6w%KG2GQ zr)nl5YAxYwW6MK8FA$ol1C~!ks;!_o!X*TB1%3JoH&Z&S9r`YQ3BIfbhqb0e-_<@| zX8*;XL+_4Z89P5z$#OqYL!*W? zw`9?p$RwUvzBDBNM{vs6J@sL8dXX`JtM1&#AlN~l@mfVF>4i7CWbgPzzszPi)rUnD zN`DWz1a7ADis{(OC%*dt58ZI&RrOkfwv{Pf3OM1l(Zr-(vnwy?o!Q|;;jJG|%v7l@ z?1&NBnLRnI#bF9~E(h^tEZ?knkfX0-2}!FuR;X20QqnZ4mVq}h!te&+ljLbLuX3a7 zbyY`Ob>T%LszB)sog8QHu+ry17Cz*>o}m-QS10xwbLj=7tGXM>Q>a zucxk^#`0omYv91ebx;+#NeVJob1v(%#?(aiO%y5;5!MG%cB!^8d(q=-9C1d<{XQ^O zH2G!#t4kpZl#Pr+Mcq#INQLUWOxQSwYK^j#TX_=|b|JWmNv5zq+jXrU61Pexmb4^W%k(Aw2Q6 zl{U~>n_`#|;j4Tq7ny#J_LBdk3;mwjtHx^X_-qZ2{?8XeEUmbzvX7ne5S&zV80UYh z^!?wz7s(L%eaA^wwX$l9(q9LOv`2B7i<=}1O(4O{P8Em^_fZ-2F%9?4ixC!jNHYed z5AV0-V`^eG^wlZMJ$AdF=0kUFL%iMr&;eg3Ca4vB=q)@I)i2xpp$17D z!E!5mF%Y>k=+7hfEZ;nSr7t~!5dn(g*dH2C+~H%jxn zYiBX8;Z|N>Z!PHP80}7I+3)7AO25Ngky4|UiMNL?a@wrh#Att+I>p&0N2D}g?u`5N zQ}^(#`*GrNFGLfd=|AIne45y^Zh{g~Jx>i-zpE&26s>AJWR6f~6S$o&UnfdS@2Jpo z^NS&qq$xqUFSsIKW~E2KjKNpX>Q)=%tp`$Rrf0Vv#$Yg{9etNc%Utq9{^5QvvmmdP zu9m644R?ekQzom)B6E7ZqZJ0l-50m3{P=38PAAJ)Hzsr0p4Wwl``I?ay*R9kEvFV5r_HONWIG5Z!cx(2rn?i^xOK4;keCg?h?nonj}5u zOA0e!v9_KKT4V<2;>rzS-<0|2&k~nIcx(3-ldSnrUP&l^V>FK|eV-N+2NCw~#5zhx zb`22a=s(h%E%*ZXe8DkcwfGakv=v5Jb)ML<`wxevl8}9xm2f4t1lCj#Lu$9Ocbu#; zYgAl*DPZb11LnjGT`r1~^#GHcs0B-tH4XdRye*^)zzXScqOB{!rz_V{x-#T$0fTdM zsxQENF{$%U?_yk%pv^2jFwc2pB~p?iqRdsB3cRDcGM`&!Q&jeoozJw>hApH=T-b^3 z2r8+$VsF4AG&SS%BUkaLJSr7O)f$I9OPap@XfrjRD{=^#Nf4ggJiRq@oiK`*ge$iv zj1Ozc%=CB@84c=(&F%^)Q9Mj6MoN`+lUDeY$8ap|iHEn0MC#&g)z{5`3z#Lc)0yQ- zrF&>STGNW{M_0VhNyWW3=hfBUL_IJPOQw6Qa!ta+gE`bglC@v*Mn63>i1q=Yy`HTX z|DH1!bt#vxS|d>xWRsLR@ME7twfgO5-Eb1nt4P~d-yPQ=IiN_;_stz>J~-#PR)3Y@ zwk@`_m&p)N45yAgNKo$Vw)L)L9VVHja@w6~q4#k1nuGkLk3TwDZx3pKxnP<@=sP_Z9OGrVuXuGq}P+kz$X#l>#}>m-(oZ270b=vbl#acy0p}% zeUB{fGF+toXr?-Kv#QTm7y6bsPYS;9cxHjAzW2cno#M8a|4#wiSV8MATVY&r+SG<| zB?x+Tju6%)T}D2=r9Q-%C)+8|!?M()^@JrIQOq5edKa7apgOGto1Tundh5FEn?$^E z4^u#Ij)rdOO6fIg0}qamVN^)9nI4UQ`3;8*7ehER$?OIICl@_0a!Q4RM{k=G zck&-l(-E#8hI6Qlfd%4v?*vbIuef?+^`j3Qu0>qARte!E}=)oVgZjFR4ell-)e@Hg-xg|j%p5;zm=H3x--|v>SE{dw1`kpwqsts6VkkMslDc~ zl$@Mw_wVu9Wh=3``SZQa9lm!t_@SOBNq1wZsXtbh42#~atk#iK?+I973Nv}D;V~mX zN!<|SjyYiYd@sk6pIYtcGRvB_VF(9Rj;9SHHh}TzYprd&qjQqZBf z<*?lqNcSw~{j*wFC+$o_ zZF#CnM?An>5(!*IAV#j5OgdJWo7idU?l=@p$b|1bYJi%><8Ku`2?zV9r>uU4z7PEx zgJ-)P-80w89pR4)j@{vhK20=|nzZ;j$!|O*yfWqc&4l*pVaXsTGl+*-0D{z4!E19H z{QDp2D{Y^Sfr&q=34WHjadf0DIRg&s(4i<>5UR63=N0Kdbz}gYya}A-5J<>4fkwHw z)jWY0|1n15naSqfDZk(wDLtJBBdy&@JYG!q{((PJun>x-F#u#I?bK zh#r1ngv#E&g0c4RyTBi@_#L6(k>7~F{BSkGxsId<*f(+&7rg4BnFwx4pthz~R@kR4 zN`TIEON`wQ8L|vD*7`n>0-uK>=JzB1M*t}u*5aH1i*bhvJ1D&_1w(Q!3a4osiw6tm zstdPD_;P6GGHsor@z$&Y0^CQ73I-3W043Cc9IU{tnXYEyi>81BI9x*}7@6YGTFD8S zkpV*{P;xCW0~-_u7AwLC+=CNT0VYg?A?Myp6hT39Ng4ZN^8zxeesEH(C6~Ysb!3ek zFU8QVu=nC13>TUV`?0~&GQ!+22_;w4?Ql7W!yjM*B~*bE$bkf`z#{|$IEeDq_6;0# z^EZbxIOE#YR;eQ3%QXap$4Y<+obn{7fI093E;#?QnB)gFxH1OAGQ1-5u=HkkT(5(Y zuYsAc&dxDX{PKC+apfA&9{Xw^3v^AGug+p2F=e87(7+q`0S>pQi#9Vks4G8A!yl-? zEKC9=Omh{~fl508OSiO3yR=HHbQPR5B}@V)sK6g=+77E#)0L1obb<^h0Vg0#ET1ty ze+BhU2SAI5F$W<*AFM$~E-D7I^9^%|6*ENtgG9@#=XL@$-~lzX!GKNYk5H8#w85q= zBFT(k(=p3V;{sgov@^FgT^q7o$Mrn#u)6kiYl;a2Py58T9Kw@Ohj_F+dwQ;)@kL2g7owY*05_D*$CEcDh`wR0A%UefX# zh(q1tLN1uW9^k`6ggG^gF_$Fy6-`_sAOJ47fSbR0o5#5e%=4U+L!IBboy+-~&p85U z8yH^2d+RgK9{E?a+ZV`#lFLyJcmkn+g+EWn)@ZqO1bT7%vXno^9sk1|G-2*^dZ&lo zb!h07*MRO291Y7W4yb}YDD3*mGcNE$4-CUA;DMR{2%6_a6SVoB?>evdy07m#9$bJ^ z7xtjP&O216R(t^4h0z-)1(Qz)rT@33Bli)~fwf;dwqHA}3Qu$5mIvfPqL(i+s{#~g zfIoQdv6e$17(ol*LL1aNLeBpx0{pt2f3#fR`@Oq>o&UNEblE@?J0vzbR2;zu*m6;1 zgTPCLerv_EPr8$TDU{>(rPoEKUxlCrg{YIC2L#I-jr%YVfKR-^eY9^mG=rGq!d0HR znC7~^)ANXn_=l_bhnu*Fn~<*Wy0Dw#!(&H$$Et8^#j+E1xGP1&4?AQ}`$SU5#REkm zbo)|y00fX2GHXBu5WrCPLYPavcyEz^t zg3bf{Rwn$}3_Vkjfb>Q>u3p83o#|LaJF7;#O-%NH@4dzIHb!3g6JV2O_bm<(04%_Q zD*VJMjJ!POLJ0JLIl%url5-!J^tJK6wqeSXr$~3(7lGd&^v+*L&zH&x7=Bg=l@F#q z-}_)z`||3SrQp{D;ZHg2FU8UWh0|Np6Ku>+E#cquC`g$$LjB6MIQgi8jl6t1I2F-cQ2DUZU`n7fY7H(X)H9%$EFc$Avfdn;JEH(vY!*UGq z?8xgcT;appJ~?|=ck%!!2&lSPq76!h<^H@<@g{F#NirBumi%30 zcj+Hjx+p^DEbZE!lY*{2y7$p9$^G4qe_A(=Z1H8Pr_a^Cq{2#$H~#+ljVly9AdW$} zo`}G%|8(O-ryWYdB@jD$=mfj55(_1|@W>0vq?E)vsk;p2*Hj(%6Wr^t`buMvJo3IQ8GFqE738QFas+_*VseKJ=bo;aU(if)9nZY z-=XgdLWd+$C?k(TvNk2-W3s7Oo{X|V2^5la=G+F6RIpcf`q&5)Pff^FMqf<_C%8>!ayHg>ResA5V$pbcDr zMe+ZniHh+fG7(jp;uk)hoe@xUIUAJPD6BM4Y^(orWFeWq(`V36lN{tNspyQDv1&dtNEbUF@p->Q@W#S-=Ik}l> zo>VLgFXYDzJM7qlXMWn~WC}la@MDTjsKKKgTw1UL6@$k!cJj}qGif3k{HU6wBF6MC zq)vPYTgJ~UKJ089vr(JBKIl7P4npbp;+^1yAD;Mk?l@xh!%FV2?$GX*EpMR|?0f$> zDgoC`@WIFRXO$vGFS8nDWaT*!M3jjL7jXhHqlG_sZef^dJU^)u8Z7LZpB<3)L%fvM zETLGBVN4qm0Xsi|jBLrnOYIC4FV`68cFBPawLY*a3(5mO>!BNg%!M}v>cmOZ6AlDm zh7<>)CVSdr-a(+lHCpYAA3ac93tEH@3<+F+8dxSA~HL|r133l~awloN&~ zg*`sT`^D9nXtI^!9wRy4@*h{q3Y+Cd>nV#J2!?*y?}-7!DnL<34ulRpV%RXiCVI&8Bl z1_C2}j^H+3(j=A6VkOE91WTNtfR=Hw9xelS&X|axZYAhMiNw>8@d&0QgJh<6^k=^x zhNO`Ui<3wqDGwPS5hdcVq=2GW%52JnlzHi9Zw!?j1L1RbIj|=>Rawe)+Rc@japy(U zu%LLl#ZyUnBWL31MUYlx6;tS+oKR#;o{|SU#ZcrTjfgtXEn*G`fGGbztO>=5$xb&L zm5m4vRT~45U|SO0&q!sGPO+WTc_}^1N-Yu#mc6teT)3rAW_p;Ks_>>2p#cK+2`syk z$fqKrMicxYRD>n;M39Lj9hyMIk4W^I7KQ42#M&FGdXzsOm=CwO+SQWU2CSTUo*BVu z5l+k!t^Kg2OmjKXxLRZtPAe98$dlKRP@@EWUFJbQ;!LaY?gFdj8Uvbo$%@+MqA|(n z0vkKksy+~c|2aZ&wdYk0;)a7M>#S%*5!$xQldYw-=V>p(G(SA8SFde@>a-T8HpJGT zdCK4ZBooOTC}FjDTG2$0^xI4>R#40(&0}?gqa6I_DwW-cbH)D%T{24dFfFyNyXc8M zu5uSA#aKlNnl`y9M9vSAL%iEWaF=e%yr&$>vg{&9x0b4@L zg-_AM$avW^&H9`j5&&8xR4%dM0&5u41s268cyEYCf>{5jiY>Ss8SYr5x*AYIHo5;v z3|HNZSvaHg$9YL(Xou`Z?VhvMc=@w#wKnKwF4eYOL$snJ)96X1881`RW~2u4BJO15w$St6~PUL#L;$?SFw(bft*H19s#KVSRS;t6Co%_3NvL=pc=>3LbMy)!-aXNNV~8r|ESu^W@T za|P>=ZbbM+9}Z#%Xp12X z!ynYo{d6tLx`y~1&G_gqzXs=IP@{`{r|G{Io{2Nw_^7Un<=25Lm8K?&q<=#I~d2FJ`?W2*=S zZouUQ`NIdD0?LevOmG1Y$bbN#pbF;am|P$b zLcjh zK*-uC4u@h6!PLIWZo z58$o@A+7#sM)X_*2mX&*Qt&@c;0yl;rwmDE0IQ+^lfn`&q8LC6tpaW%Qc);Y(Xk}3 zDL6qD0)PMrz!7?Y0jRJ{#DNxVv2jEqb9S*10Wl;nq!x$KhxE}NLjV>MF#}Kl6ueL9 z(v9ehj+;dBDAe)j*o^(U5>~FO9{AkPS)j^mHRgxG*b6<_NxE z1LlAZ!1AI#-~&j2ROnz188sKh=m0zWfD$y9*=27*fc(+sHa7Yh;}`-deE)J_z15QULN zP6QW}VM_NDH&Y->r;L5WT;X?aNrL5AO~b@ z4!!_J4ymS~V-;YCnT?So3lSLf{nuvoNb6 z4F;4X@`X@|$S~8O8a(GQ$N^p(HC^*E1p)w^F0|;Pl_E~y9ljtN?4e$znP;)HR5q2KWZ8Y5)dg6RvUtPnYsv88ZZWAdr6Z6@t?iad9Mo zQ#g%=G-I!o zJzG*Fr?XT4L&3%o0!%i^#6kmNRcQ5+UGwoyGKWzs03Uy9PzkYJ3$!o?F&JrRS8?GF zZuM-dGzI^hBT|s{YC)7>3FYGk7h|(F4ciG)v~NBq)4>Q%1Nt^EYcUXr>E>_&6^3Ck zXSQpU^k!=|DAyHV-L)`tVO=Q~U;`jR}b z>H{WMb%U`28~}6mu^iggZH-q%1k(uS_BRJ{2-Z~@jyEuI;T1SHX!{m2SvOn+cXlOY zOt~sd!}m-T_rRJ?^K38!M3(z}Aad;q1rA|*|8-r%Kp9lPdO@Ng>{c&*^UFZjCyR7+ zDB^1y6Y zwt@eZ0Tu4Zmoldu0-=GE@@+>VbL`d-kq9`2)d(s;O=u+-6|@|h0XZr65C|Z7j@Dqq zw?ny#1e_0pB?Nta%y*KQ4N>)_P=JKb2?|v9Y(qd9@Sq9fBy(zrQMWe)_JAC&EOUO= z2m-+c0tC!r2&&gliLx1nVUGzS7}KByP@zmV z85c5HN*}l&X6KI$IW+V`!0u^%by)^4No&ngkq-o1cK4+$!FOrOo-*KpTecbaMI8U8 z;SW&Z75wM`kYOpOcaFngg>g2I<23{jAyL2VLDjW^k>LQ8!2xms75-ov#6g}NAPiQS za$x~*Dn9vP7Ve`Ptcod2xlLTdQ&K#xVmt&tj1R3T!z9(x3;Ffuhae01iM1 zGWw#0U>Ab+Nnr&bWB7(ecS?sK41{2#g+QGznxd7V2jtk6hroOF8GHr0D;j#LoY zshe7*4}lPEIVlstk866TU6ES7hy<)Qt<#jC({~yJnm!W2isQ)=ind~nnymj>`4Cpx ztDkxo7L$36g;vT~LPLOtXf`CsA$vn03CRd;%uTCy=3BFMpq=!90JH!p+patTxdtBqNdriHF$Rz}XL#uc9dZgUoHl3WzojMTyz#OU}7aTwtve~wUK+DtMt5N!o za-kZk;T4pj&@mduAK1TfVxQj)CPW|#cDyRyflLQnE3$#Q`5D3cAk|CzB5*t^KA^yf zBmr3t1@u}KA1oFIovepI!_!*`5Fw={JIf_BvM1ZN(_j}i{Q&=7oWG6OXgAv_bj|Qu z{ZpeL&ao{$O7)xDHZe%TBkel_SI4h+02 z9GT^6KosKLv`&K)@-(KG{kC_(2>d;;k-IUW{Qx>zhy*kTlUb4;n z-VdOj#onuL{+Yw!eGb4< zg>s+|zCiI8pYa>t@fSZ1?Ua&iTScd>oS=1ADpZ9k^^nLs)S2Xm8fB5xYK<1zi-T_8& zz!NqA-S>WxNuclhAPVSQLJ(GAoAt--PdvNd`@bLjzkk3<0QsZ9#$RJQH$XnEi;YIp z{l^pj9k!Rn)BQ;x51^ouW!2u(8}C&ajnJNE4&dK&=k-wchd;0vj^F)+BbV$%V ziZrRxrA(WiWJ!Wfj%;zFygRb!#0eZ+yL$b4V$q^^QQ~BqBjTgewQSqEeLIt-h&l55 zSjC!EtKP6j?tJMZuc*ldT>2S4j5x94#r-TSfD5^89w?49TFy8`p~Qg)hX~w!fMRCQ z4If4XP4Tj7i$fft$P+oXY>;&1RmHpYE7!1N|K?<4Dq7Z>YZa2nV~;-m2xNVrSP&wJPgoEnQURWL-HkFX6TpP@h738j!J5& zYk>m6hloPQ1q7xBSRD!k%qnS$mwxvUrVKtbj1ZEhKmdD>c4=y{#vW@ds;lnj0R*AG z3W^(T#tOx(v!;-Oq==-TWwzQ%N&r@{!9Mz09&UgFib=?>%Wk`a5~k|1j6DItWA5II zZ@&8KyFhdDKIh-Q0uM}Z!C?0L?{kV8%y7dFKP+E<2|HJVs}Ns|amE@q1%ba4E9ODK K8;>kzKma?o`!g>9 literal 0 HcmV?d00001 diff --git a/ansible/roles/clickhouse/meta/main.yml b/ansible/roles/clickhouse/meta/main.yml new file mode 100644 index 00000000..06e5e050 --- /dev/null +++ b/ansible/roles/clickhouse/meta/main.yml @@ -0,0 +1,17 @@ +--- +galaxy_info: + role_name: clickhouse + author: idealista + company: Idealista S.A.U. + description: Clickhouse Role + min_ansible_version: "2.9.9" + license: Apache 2.0 + galaxy_tags: + - clickhouse + - db + - database + - install + platforms: + - name: Debian + versions: + - bullseye diff --git a/ansible/roles/clickhouse/molecule/default/Dockerfile.j2 b/ansible/roles/clickhouse/molecule/default/Dockerfile.j2 new file mode 100644 index 00000000..141d8fdf --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/Dockerfile.j2 @@ -0,0 +1,18 @@ +# Molecule managed + +{% if item.registry is defined %} +FROM {{ item.registry.url }}/{{ item.image }} +{% else %} +FROM {{ item.image }} +{% endif %} + +# install minimal packages for debian slim images +{% if item.image == 'debian:bullseye-slim' %} +RUN apt-get update && \ + apt-get install -y python3 sudo bash ca-certificates iproute2 systemd systemd-sysv python3-pip && \ + apt-get clean +{% else %} +RUN apt-get update && \ + apt-get install -y python sudo bash ca-certificates iproute2 systemd systemd-sysv python-pip && \ + apt-get clean +{% endif %} diff --git a/ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml b/ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml new file mode 100644 index 00000000..53ceca26 --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ansible/roles/clickhouse/molecule/default/config_files/config/config.xml b/ansible/roles/clickhouse/molecule/default/config_files/config/config.xml new file mode 100644 index 00000000..2494a08c --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/config_files/config/config.xml @@ -0,0 +1,1257 @@ + + + + + + + trace + /var/log/clickhouse-server/clickhouse-server.log + /var/log/clickhouse-server/clickhouse-server.err.log + + 1000M + 10 + + + + + + + + + + + + + + + + + + 8123 + + + 9000 + + + 9004 + + + 9005 + + + + + + + + + + + + 9009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4096 + + + 3 + + + + + false + + + /path/to/ssl_cert_file + /path/to/ssl_key_file + + + false + + + /path/to/ssl_ca_cert_file + + + deflate + + + medium + + + -1 + -1 + + + false + + + + + + + /etc/clickhouse-server/server.crt + /etc/clickhouse-server/server.key + + /etc/clickhouse-server/dhparam.pem + none + true + true + sslv2,sslv3 + true + + + + true + true + sslv2,sslv3 + true + + + + RejectCertificateHandler + + + + + + + + + 100 + + + 0 + + + + 10000 + + + 0.9 + + + 4194304 + + + 0 + + + + + + 8589934592 + + + 5368709120 + + + + 1000 + + + 134217728 + + + 10000 + + + /var/lib/clickhouse/ + + + /var/lib/clickhouse/tmp/ + + + + + + /var/lib/clickhouse/user_files/ + + + + + + + + + + + + + users.xml + + + + /var/lib/clickhouse/access/ + + + + + + + default + + + + + + + + + + + + default + + + + + + + + + true + + + false + + ' | sed -e 's|.*>\(.*\)<.*|\1|') + wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge_$PKG_VER-1_all.deb + apt install --no-install-recommends -f ./clickhouse-jdbc-bridge_$PKG_VER-1_all.deb + clickhouse-jdbc-bridge & + + * [CentOS/RHEL] + export MVN_URL=https://repo1.maven.org/maven2/ru/yandex/clickhouse/clickhouse-jdbc-bridge + export PKG_VER=$(curl -sL $MVN_URL/maven-metadata.xml | grep '' | sed -e 's|.*>\(.*\)<.*|\1|') + wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm + yum localinstall -y clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm + clickhouse-jdbc-bridge & + + Please refer to https://github.com/ClickHouse/clickhouse-jdbc-bridge#usage for more information. + ]]> + + + + + + + + + + + + + + + + localhost + 9000 + + + + + + + + + localhost + 9000 + + + + + localhost + 9000 + + + + + + + 127.0.0.1 + 9000 + + + + + 127.0.0.2 + 9000 + + + + + + true + + 127.0.0.1 + 9000 + + + + true + + 127.0.0.2 + 9000 + + + + + + + localhost + 9440 + 1 + + + + + + + localhost + 9000 + + + + + localhost + 1 + + + + + + + + + + + + + + + + + + + + + + + + 3600 + + + + 3600 + + + 60 + + + + + + + + + + + + + system + query_log
+ + toYYYYMM(event_date) + + + + + + 7500 +
+ + + + system + trace_log
+ + toYYYYMM(event_date) + 7500 +
+ + + + system + query_thread_log
+ toYYYYMM(event_date) + 7500 +
+ + + + system + query_views_log
+ toYYYYMM(event_date) + 7500 +
+ + + + system + part_log
+ toYYYYMM(event_date) + 7500 +
+ + + + + + system + metric_log
+ 7500 + 1000 +
+ + + + system + asynchronous_metric_log
+ + 7000 +
+ + + + + + engine MergeTree + partition by toYYYYMM(finish_date) + order by (finish_date, finish_time_us, trace_id) + + system + opentelemetry_span_log
+ 7500 +
+ + + + + system + crash_log
+ + + 1000 +
+ + + + system + session_log
+ + toYYYYMM(event_date) + 7500 +
+ + + + + + + + + + + + + + + + + + *_dictionary.xml + + + *_function.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /clickhouse/task_queue/ddl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + click_cost + any + + 0 + 3600 + + + 86400 + 60 + + + + max + + 0 + 60 + + + 3600 + 300 + + + 86400 + 3600 + + + + + + /var/lib/clickhouse/format_schemas/ + + + + + hide encrypt/decrypt arguments + ((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\) + + \1(???) + + + + + + + + + + false + + false + + + https://6f33034cfe684dd7a3ab9875e57b1c8d@o388870.ingest.sentry.io/5226277 + + + + + + + +
diff --git a/ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml b/ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml new file mode 100644 index 00000000..f24b19bd --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml @@ -0,0 +1,24 @@ + + + + trace + /var/log/clickhouse-server/clickhouse-copier.log + /var/log/clickhouse-server/clickhouse-copier.err.log + 1000M + 10 + + + + zookeeper-test1 + 2181 + + + zookeeper-test2 + 2181 + + + zookeeper-test3 + 2181 + + + diff --git a/ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml b/ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml new file mode 100644 index 00000000..ae6a6b94 --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml @@ -0,0 +1,8 @@ + + + + 0 + 0 + + + \ No newline at end of file diff --git a/ansible/roles/clickhouse/molecule/default/config_files/users/users.xml b/ansible/roles/clickhouse/molecule/default/config_files/users/users.xml new file mode 100644 index 00000000..f59f2832 --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/config_files/users/users.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + 10000000000 + + + random + + + + + 1 + + + + + + + + + + + + + ::/0 + + + + default + + + default + + + + + + + + + + + + + + 3600 + + + 0 + 0 + 0 + 0 + 0 + + + + diff --git a/ansible/roles/clickhouse/molecule/default/converge.yml b/ansible/roles/clickhouse/molecule/default/converge.yml new file mode 100644 index 00000000..2affbef0 --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/converge.yml @@ -0,0 +1,5 @@ +--- +- name: Converge + hosts: clickhouse_group + roles: + - role: clickhouse_role diff --git a/ansible/roles/clickhouse/molecule/default/files/jdbc-drivers/file.drivers.here b/ansible/roles/clickhouse/molecule/default/files/jdbc-drivers/file.drivers.here new file mode 100644 index 00000000..e69de29b diff --git a/ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml b/ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml new file mode 100644 index 00000000..035d4195 --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml @@ -0,0 +1,226 @@ +--- + +# Clickhouse role management +clickhouse_install_db: True +clickhouse_role_manage_users: True +clickhouse_role_manage_roles: True +clickhouse_role_manage_databases: True +clickhouse_role_manage_settings_profiles: True +clickhouse_role_manage_quotas: True +clickhouse_role_manage_grants: True + +clickhouse_private_tmp: false + +clickhouse_custom_extra_config_file_path: "{{ playbook_dir }}/config_files/config/config.d" +clickhouse_custom_extra_users_file_path: "{{ playbook_dir }}/config_files/users/users.d" + +# Users +clickhouse_admin_user: admin +clickhouse_admin_password: admin + +clickhouse_admin_users: + - user: + name: "{{ clickhouse_admin_user }}" + password: "{{ clickhouse_admin_password }}" + networks: "{{ clickhouse_default_networks }}" + profile: "default" + quota: "default" + access_management: 1 + named_collection_control: 1 + show_named_collections: 1 + show_named_collections_secrets: 1 + +clickhouse_custom_users_xml: + - user: + name: "Bunta" + password: "TofuShop" + networks: "{{ clickhouse_default_networks }}" + profile: "default" + quota: "default" + databases: [ex_racing] + +clickhouse_custom_users: + - user: + name: "Takumi" + password_type: PLAINTEXT_PASSWORD + password: "AE86" + networks: + - "IP '::/0'" + settings: + - "max_memory_usage = 10000000000" + role: + - projectd_members + - tofu_shop + profile: + - default + grantees: + - projectd_members + quota: "default" + databases: [ProjectD] + - user: + name: "Iketani" + password_type: PLAINTEXT_PASSWORD + password: "S13" + networks: + - "IP '::/0'" + - user: + name: "Ryosuke" + password_type: PLAINTEXT_PASSWORD + password: "PjD4ever" + networks: + - "IP '::/0'" + databases: [ProjectD] + grantees: + - projectd_members + - -tofu_shop + profile: + - default + - max_memory_usage_profile +# ldap_server: adf.com +# kerberos: "" + +# Roles +clickhouse_custom_roles: + - role: + name: projectd_members + settings: + - "max_memory_usage = 10000000000" + - role: + name: tofu_shop + settings: + - "max_memory_usage = 100" + - role: + name: initial_d + +clickhouse_custom_grants: + - on: + databases: [Akina] + tables: ["*"] + privileges: [SELECT] + to: [initial_d] + - on: + # cluster: + databases: ["racing"] + tables: ["*"] + # columns: ["*"] + privileges: [ALL] + to: [Takumi, Iketani] + - on: + # cluster: + databases: ["Akina"] + tables: [calendar, records] + # columns: [Notes] + privileges: [ALL] + to: [Takumi, Iketani] + with_grant_option: True + - on: + # cluster: + databases: ["Akina"] + tables: [records] + privileges: [SELECT, UPDATE] + to: [Iketani] + with_grant_option: False + +clickhouse_custom_grant_roles: + - roles: [initial_d] + to: [Takumi, Iketani] + # cluster: + +# Settings profiles +clickhouse_custom_settings_profiles: + - profile: + name: max_memory_usage_profile + settings: + - max_memory_usage = 100000001 + +# Quotas +clickhouse_custom_quotas: + - quota: + name: example_quota + # keyed: ip_address # One of this: user_name | ip_address | client_key | client_key,user_name | client_key,ip_address | NOT KEYED + settings: + - "INTERVAL 30 minute MAX execution_time = 0.5" + to: # Use a minus to use ALL EXCEPT e.g: -test_role + - tofu_shop + - quota: + name: example_quota_except + keyed: NOT KEYED + settings: + - "INTERVAL 300 minute MAX execution_time = 0.5" + to: + - -tofu_shop + +# Databases +clickhouse_custom_databases: + - name: ProjectD + engine: Atomic + comment: PjD_data + - name: racing + - name: ex_racing + +# Configuration +clickhouse_listen_hosts: + - 0.0.0.0 + +clickhouse_default_networks: + - '::/0' + +clickhouse_macros: + - macro: | + 1 + 1 + server: + - clickhouse + - macro: | + default + +# Copier Configuration +clickhouse_copier_logger_level: information +clickhouse_copier_logger_log: /var/log/clickhouse-server/clickhouse-copier.log +clickhouse_copier_logger_errorlog: /var/log/clickhouse-server/clickhouse-copier.err.log +clickhouse_copier_logger_size: 1000M +clickhouse_copier_logger_count: 10 +clickhouse_zookeeper: + - node: + host: zookeeper-test1 + port: 2181 + - node: + host: zookeeper-test2 + port: 2181 + - node: + host: zookeeper-test3 + port: 2181 + +# JDBC Bridge +clickhouse_jdbc_bridge_service: clickhouse-jdbc-bridge +clickhouse_jdbc_bridge_user: "{{ clickhouse_user }}" +clickhouse_jdbc_bridge_group: "{{ clickhouse_group }}" +clickhouse_jdbc_bridge_service_template_path: jdbc-bridge/clickhouse-jdbc-bridge.service.j2 +clickhouse_jdbc_bridge_service_state: started +clickhouse_jdbc_bridge_service_enabled: True +clickhouse_jdbc_bridge_config_path: "/etc/clickhouse-jdbc-bridge" +clickhouse_jdbc_bridge_file_drivers_path: "{{ playbook_dir }}/files/jdbc-drivers" +clickhouse_jdbc_bridge_drivers_path: "{{ clickhouse_base_path }}/drivers/" +clickhouse_jdbc_bridge_documentation_link: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/blob/v{{ clickhouse_jdbc_bridge_version }}/README.md" +clickhouse_jdbc_bridge_log_path: /var/log/clickhouse-jdbc-bridge +clickhouse_jdbc_bridge_log_file: "{{ clickhouse_jdbc_bridge_log_path }}/clickhouse-jdbc-bridge.log" +clickhouse_jdbc_bridge_log_level: DEBUG +clickhouse_jdbc_bridge_env: | + JVM_ARGS="-Xms1024m -Xmx2048m" +clickhouse_jdbc_bridge_version: 2.1.0 +clickhouse_jdbc_bridge_deb_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge_{{ clickhouse_jdbc_bridge_version }}-1_all.deb" +# clickhouse_jdbc_bridge_rpm_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge-{{ clickhouse_jdbc_bridge_version }}-1.noarch.rpm" +clickhouse_jdbc_bridge: + host: 127.0.0.1 + port: 9019 +clickhouse_jdbc_bridge_datasources_path: "{{ clickhouse_jdbc_bridge_config_path }}/config/datasources" +clickhouse_jdbc_bridge_datasources: + - name: clickhouse_test + datasource: + $schema: "../datasource.jschema" + clickhouse: + driverUrls: ["https://repo1.maven.org/maven2/com/clickhouse/clickhouse-jdbc/0.3.2-patch8/clickhouse-jdbc-0.3.2-patch8-http.jar"] + driverClassName: "com.clickhouse.jdbc.ClickHouseDriver" + jdbcUrl: "jdbc:clickhouse://localhost:8123/system?compress=false" + username: "{{ clickhouse_admin_user }}" + password: "{{ clickhouse_admin_password }}" diff --git a/ansible/roles/clickhouse/molecule/default/molecule.yml b/ansible/roles/clickhouse/molecule/default/molecule.yml new file mode 100644 index 00000000..6b95eb1e --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/molecule.yml @@ -0,0 +1,50 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +lint: | + yamllint . + ansible-lint . + +platforms: + - name: clickhouse + groups: + - clickhouse_group + image: ${MOLECULE_DISTRO:-debian:bullseye-slim} + privileged: false + capabilities: + - SYS_ADMIN + tmpfs: + - /tmp + - /run + - /run/lock + volumes: + - '/sys/fs/cgroup:/sys/fs/cgroup:rw' + command: ${MOLECULE_DOCKER_COMMAND:-"/lib/systemd/systemd"} + cgroupns_mode: host + stop_signal: 'RTMIN+3' + exposed_ports: + - 8123/tcp + - 9000/tcp + - 9019/tcp + published_ports: + - 0.0.0.0:8123:8123/tcp + - 0.0.0.0:9000:9000/tcp + - 0.0.0.0:9019:9019/tcp + +provisioner: + name: ansible + lint: + name: ansible-lint + log: True + options: + vvv: True + +scenario: + name: default + +verifier: + name: ansible diff --git a/ansible/roles/clickhouse/molecule/default/tests/test_app.yml b/ansible/roles/clickhouse/molecule/default/tests/test_app.yml new file mode 100644 index 00000000..e27aefff --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/tests/test_app.yml @@ -0,0 +1,98 @@ +--- + +file: + {{ clickhouse_base_path }}: + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: directory + {{ clickhouse_config_directory }}: + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: directory + {{ clickhouse_log_directory }}: + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: directory + {{ clickhouse_tmp_directory }}: + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: directory + "{{ clickhouse_config_directory }}/{{ clickhouse_config_file_name }}": + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: file + "{{ clickhouse_config_directory }}/{{ clickhouse_users_file_name }}": + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: file + "{{ clickhouse_config_directory_config_d }}/log_disable.xml": + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: file + "{{ clickhouse_config_directory_users_d }}/log_queries.xml": + owner: {{ clickhouse_user }} + group: {{ clickhouse_group }} + exists: true + filetype: file + +service: + {{ clickhouse_service }}: + enabled: true + running: true + +port: + tcp:{{ clickhouse_tcp_port }}: + listening: true + +user: + {{ clickhouse_user }}: + exists: true + groups: + - {{ clickhouse_group }} + shell: /usr/sbin/nologin + +group: + {{ clickhouse_group }}: + exists: true + +command: + clickhouse-server_version: + exit-status: 0 + exec: "clickhouse-server --version" + stdout: + - "{{ clickhouse_version }}" + stderr: [] + timeout: 10000 + skip: false + clickhouse-client_version: + exit-status: 0 + exec: "clickhouse-client --version" + stdout: + - "{{ clickhouse_version }}" + stderr: [] + timeout: 10000 + skip: false + +http: + http://localhost:8123?query=SHOW+USERS: + status: 200 + timeout: 10000 + body: ["admin", "default", "readonly", "Bunta", "Takumi"] + username: {{ clickhouse_admin_user }} + password: {{ clickhouse_admin_password }} + skip: false + + http://localhost:8123?query=SHOW+DATABASES: + status: 200 + timeout: 10000 + body: ["system", "default", "ProjectD", "ex_racing", "racing"] + username: {{ clickhouse_admin_user }} + password: {{ clickhouse_admin_password }} + skip: false diff --git a/ansible/roles/clickhouse/molecule/default/verify.yml b/ansible/roles/clickhouse/molecule/default/verify.yml new file mode 100644 index 00000000..dd574112 --- /dev/null +++ b/ansible/roles/clickhouse/molecule/default/verify.yml @@ -0,0 +1,60 @@ +--- +# This is an example playbook to execute goss tests. +# Tests need distributed to the appropriate ansible host/groups +# prior to execution by `goss validate`. + +- name: Verify clickhouse + hosts: + - clickhouse + become: true + vars: + goss_version: v0.3.13 + goss_arch: amd64 + goss_dst: /usr/local/bin/goss + goss_sha256sum: eb3522ff9682736ff61e2ad114de227de98debcf8a03ca66fcda3917577313e0 + goss_url: "https://github.com/aelsabbahy/goss/releases/download/{{ goss_version }}/goss-linux-{{ goss_arch }}" + goss_test_directory: /tmp + goss_format: documentation + + vars_files: + - ../../defaults/main.yml + - group_vars/clickhouse_group.yml + + tasks: + - name: Download and install Goss + get_url: + url: "{{ goss_url }}" + dest: "{{ goss_dst }}" + checksum: "sha256:{{ goss_sha256sum }}" + mode: 0755 + register: download_goss + until: download_goss is succeeded + retries: 3 + + - name: Copy Goss tests to remote + template: + src: "{{ item }}" + dest: "{{ goss_test_directory }}/{{ item | basename }}" + with_fileglob: + - "tests/test_*.yml" + + - name: Register test files + shell: "ls {{ goss_test_directory }}/test_*.yml" + register: test_files + + - name: Execute Goss tests + command: "{{ goss_dst }} -g {{ item }} validate --format {{ goss_format }}" + register: test_results + with_items: "{{ test_files.stdout_lines }}" + ignore_errors: true + + - name: Display details about the Goss results + debug: + msg: "{{ item.stdout_lines }}" + with_items: "{{ test_results.results }}" + + - name: Fail when tests fail + fail: + msg: "Goss failed to validate" + when: item.rc != 0 + with_items: "{{ test_results.results }}" diff --git a/ansible/roles/clickhouse/tasks/config/clickhouse.yml b/ansible/roles/clickhouse/tasks/config/clickhouse.yml new file mode 100644 index 00000000..ec37eb4f --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/clickhouse.yml @@ -0,0 +1,65 @@ +--- + +- name: CLICKHOUSE | Add configuration files (from vars) + ansible.builtin.template: + src: "{{ clickhouse_config_file_name }}.j2" + dest: "{{ clickhouse_config_directory }}/{{ clickhouse_config_file_name }}" + mode: 0644 + when: clickhouse_custom_config_file_path is not defined + notify: "{{ clickhouse_handler_on_config_change }}" + tags: clickhouse_configure + +- name: CLICKHOUSE | Add configuration files (from user files) + ansible.builtin.copy: + src: "{{ clickhouse_custom_config_file_path }}/" + dest: "{{ clickhouse_config_directory }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: 0644 + when: clickhouse_custom_config_file_path is defined + notify: "{{ clickhouse_handler_on_config_change }}" + tags: clickhouse_configure + +- name: CLICKHOUSE | Add extra configuration files + ansible.builtin.copy: + src: "{{ clickhouse_custom_extra_config_file_path }}/" + dest: "{{ clickhouse_config_directory_config_d }}/" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: 0644 + when: clickhouse_custom_extra_config_file_path is defined + tags: clickhouse_configure + +- name: CLICKHOUSE | Configure users (from vars) + ansible.builtin.template: + src: "{{ clickhouse_users_file_name }}.j2" + dest: "{{ clickhouse_config_directory }}/{{ clickhouse_users_file_name }}" + mode: 0644 + when: clickhouse_custom_users_file_path is not defined + notify: "{{ clickhouse_handler_on_config_change }}" + tags: + - clickhouse_configure + - clickhouse_users + +- name: CLICKHOUSE | Configure users files (from users files) + ansible.builtin.copy: + src: "{{ clickhouse_custom_users_file_path }}/" + dest: "{{ clickhouse_config_directory }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: 0644 + when: clickhouse_custom_users_file_path is defined + notify: "{{ clickhouse_handler_on_config_change }}" + tags: + - clickhouse_configure + - clickhouse_users + +- name: CLICKHOUSE | Add extra user configuration files + ansible.builtin.copy: + src: "{{ clickhouse_custom_extra_users_file_path }}/" + dest: "{{ clickhouse_config_directory_users_d }}/" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: 0644 + when: clickhouse_custom_extra_users_file_path is defined + tags: clickhouse_configure diff --git a/ansible/roles/clickhouse/tasks/config/copier.yml b/ansible/roles/clickhouse/tasks/config/copier.yml new file mode 100644 index 00000000..3613c943 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/copier.yml @@ -0,0 +1,21 @@ +--- + +- name: CLICKHOUSE | Add copier configuration files (from vars) + ansible.builtin.template: + src: "{{ clickhouse_copier_config_file_name }}.j2" + dest: "{{ clickhouse_config_directory }}/{{ clickhouse_copier_config_file_name }}" + mode: 0644 + when: clickhouse_copier_custom_config_file_path is not defined + notify: "{{ clickhouse_handler_on_config_change }}" + tags: clickhouse_configure_copier + +- name: CLICKHOUSE | Add copier configuration files (from user files) + ansible.builtin.copy: + src: "{{ clickhouse_copier_custom_config_file_path }}/" + dest: "{{ clickhouse_copier_config_directory }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: 0644 + when: clickhouse_copier_custom_config_file_path is defined + notify: "{{ clickhouse_handler_on_config_change }}" + tags: clickhouse_configure_copier diff --git a/ansible/roles/clickhouse/tasks/config/databases.yml b/ansible/roles/clickhouse/tasks/config/databases.yml new file mode 100644 index 00000000..ff1a665f --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/databases.yml @@ -0,0 +1,49 @@ +--- + +- name: CLICKHOUSE | Create default database (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q 'CREATE DATABASE IF NOT EXISTS `{{ clickhouse_default_database }}`'" # noqa 204 + changed_when: False + when: clickhouse_default_database is defined + no_log: True + tags: + - clickhouse_databases + - create + - SQL_driven + +- name: CLICKHOUSE | List databases (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.databases WHERE LOWER(name) NOT IN ('_temporary_and_external_tables', 'information_schema', 'default', 'system');\"" + changed_when: False + register: clickhouse_existing_sql_databases + no_log: True + tags: + - clickhouse_databases + - select + - SQL_driven + +- name: CLICKHOUSE | Drop databases (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP DATABASE IF EXISTS {{ item }} ;\"" + with_items: "{{ clickhouse_existing_sql_databases.stdout_lines }}" + when: + - clickhouse_existing_sql_databases is defined + - item not in clickhouse_custom_databases | map(attribute='name') | list + no_log: True + register: drop_databases + changed_when: drop_databases.rc != 0 + tags: + - clickhouse_databases + - delete + - SQL_driven + +- name: CLICKHOUSE | Create custom databases (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/DATABASE.j2') | quote }}" + with_items: "{{ clickhouse_custom_databases }}" + when: + - clickhouse_custom_databases is defined + - item.name not in clickhouse_existing_sql_databases.stdout_lines + no_log: True + register: add_databases + changed_when: add_databases.rc != 0 + tags: + - clickhouse_databases + - create + - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/grants-roles.yml b/ansible/roles/clickhouse/tasks/config/grants-roles.yml new file mode 100644 index 00000000..40f8d787 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/grants-roles.yml @@ -0,0 +1,12 @@ +--- + +- name: CLICKHOUSE | Assigning roles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} --multiquery -q {{ lookup('template', 'DDL/GRANT_ROLE.j2') | quote }}" + when: clickhouse_custom_grant_roles is defined + register: assigning_roles + changed_when: assigning_roles.rc != 0 + no_log: True + tags: + - clickhouse_grants + - grant + - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/grants.yml b/ansible/roles/clickhouse/tasks/config/grants.yml new file mode 100644 index 00000000..d1a3dcd9 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/grants.yml @@ -0,0 +1,55 @@ +--- + +- name: CLICKHOUSE | List perms and privs (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \" + SELECT user_name, role_name, + ifNull(database, '*') AS database , + ifNull(table, '*') AS table, + ifNull(column, '*') AS column, + arrayCompact(groupArray(access_type)) AS access_type, + grant_option + FROM system.grants + RIGHT JOIN system.users u ON user_name = u.name AND u.storage = 'local directory' + WHERE (user_name in ({{ '' ~ item.to ~ '' | join(', ') }}) or (role_name in ({{ '' ~ item.to ~ '' | join(', ') }}))) + AND database in ({{ '' ~ item.databases ~ '' | join(', ') }}) + AND table in ({{ '' ~ item.tables ~ '' | join(', ') }}) + GROUP BY user_name, role_name, database, table, column, grant_option + FORMAT JSON; + \"" + changed_when: False + register: clickhouse_existing_sql_grants + no_log: True + tags: + - clickhouse_grants + - select + - SQL_driven + +- name: CLICKHOUSE | Set var with the perms and privs listed + ansible.builtin.set_fact: + actual_privs: "{{ (clickhouse_existing_sql_grants.stdout | from_json)['data'] }}" + tags: + - clickhouse_grants + +# All logic of this revokes and grants are in the template because of "speed performance" instead of ansible task doing the loop +- name: CLICKHOUSE | Revoke user and roles grants missing (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} --multiquery -q {{ lookup('template', 'DDL/REVOKE.j2') | quote }}" + when: actual_privs is defined + register: revoke_grants_missing + changed_when: revoke_grants_missing.rc != 0 + no_log: True + tags: + - clickhouse_grants + - revoke + - SQL_driven + +- name: CLICKHOUSE | Do Grants for users and roles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} --multiquery -q {{ lookup('template', 'DDL/GRANT.j2') | quote }}" + when: + - item.privileges is defined + register: do_grants + changed_when: do_grants.rc != 0 + no_log: True + tags: + - clickhouse_grants + - grant + - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/perms_and_privs.yml b/ansible/roles/clickhouse/tasks/config/perms_and_privs.yml new file mode 100644 index 00000000..6fcd2bfe --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/perms_and_privs.yml @@ -0,0 +1,41 @@ +--- + +- name: CLICKHOUSE | Get all LDAP users for grants cleaning (Optional step) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \" + SELECT name FROM system.users WHERE storage == 'ldap'; + \"" + register: ldap_user_list + changed_when: False + when: clickhouse_custom_grants_previous_cleanup + no_log: True + tags: + - clickhouse_grants + - select + - SQL_driven + +- name: CLICKHOUSE | Previous grants cleaning (Optional step) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \" + REVOKE ALL ON *.* FROM ALL EXCEPT {{ ((clickhouse_default_users | default([]) + clickhouse_admin_users | default([]) + clickhouse_custom_users_xml | default([])) | map(attribute='name') | list + (ldap_user_list.stdout_lines | default([]))) | join(', ') }}; + \"" + register: grants_clean + changed_when: grants_clean.rc != 0 + when: clickhouse_custom_grants_previous_cleanup + no_log: True + tags: + - clickhouse_grants + - select + - SQL_driven + +- name: CLICKHOUSE | Grant perms & privs + ansible.builtin.include_tasks: config/grants.yml + with_items: "{{ clickhouse_custom_grants }}" + when: clickhouse_custom_grants is defined + tags: + - clickhouse_grants + +- name: CLICKHOUSE | Grant roles + ansible.builtin.include_tasks: config/grants-roles.yml + with_items: "{{ clickhouse_custom_grant_roles }}" + when: clickhouse_custom_grant_roles is defined + tags: + - clickhouse_grants diff --git a/ansible/roles/clickhouse/tasks/config/quotas.yml b/ansible/roles/clickhouse/tasks/config/quotas.yml new file mode 100644 index 00000000..0dbdaa29 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/quotas.yml @@ -0,0 +1,39 @@ +--- + +- name: CLICKHOUSE | List quotas (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.quotas WHERE storage == 'local directory';\"" + changed_when: False + register: clickhouse_existing_sql_quotas + no_log: True + tags: + - clickhouse_quotas + - select + - SQL_driven + +- name: CLICKHOUSE | Drop quotas (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP QUOTA IF EXISTS {{ item }} ;\"" + with_items: "{{ clickhouse_existing_sql_quotas.stdout_lines }}" + when: + - clickhouse_existing_sql_quotas is defined + - item not in clickhouse_custom_quotas | map(attribute='name') | list + no_log: True + register: drop_quotas + changed_when: drop_quotas.rc != 0 + tags: + - clickhouse_quotas + - delete + - SQL_driven + +- name: CLICKHOUSE | Add quotas (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/QUOTA.j2') | quote }}" + with_items: "{{ clickhouse_custom_quotas }}" + when: + - clickhouse_custom_quotas is defined + - item.name not in clickhouse_existing_sql_quotas.stdout_lines + no_log: True + register: add_quotas + changed_when: add_quotas.rc != 0 + tags: + - clickhouse_quotas + - create + - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/roles.yml b/ansible/roles/clickhouse/tasks/config/roles.yml new file mode 100644 index 00000000..348a44e6 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/roles.yml @@ -0,0 +1,39 @@ +--- + +- name: CLICKHOUSE | List roles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.roles WHERE storage == 'local directory';\"" + changed_when: False + register: clickhouse_existing_sql_roles + no_log: True + tags: + - clickhouse_roles + - select + - SQL_driven + +- name: CLICKHOUSE | Drop roles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP ROLE IF EXISTS '{{ item }}' ;\"" + with_items: "{{ clickhouse_existing_sql_roles.stdout_lines }}" + when: + - clickhouse_existing_sql_roles is defined + - item not in clickhouse_custom_roles | map(attribute='name') | list + no_log: True + register: drop_roles + changed_when: drop_roles.rc != 0 + tags: + - clickhouse_roles + - delete + - SQL_driven + +- name: CLICKHOUSE | Add roles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/ROLE.j2') | quote }}" + with_items: "{{ clickhouse_custom_roles }}" + when: + - clickhouse_custom_roles is defined + - item.name not in clickhouse_existing_sql_roles.stdout_lines + no_log: True + register: add_roles + changed_when: add_roles.rc != 0 + tags: + - clickhouse_roles + - create + - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/settings-profiles.yml b/ansible/roles/clickhouse/tasks/config/settings-profiles.yml new file mode 100644 index 00000000..12e55934 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/settings-profiles.yml @@ -0,0 +1,39 @@ +--- + +- name: CLICKHOUSE | List settings profiles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.settings_profiles WHERE storage == 'local directory';\"" + changed_when: False + register: clickhouse_existing_sql_settings_profiles + no_log: True + tags: + - clickhouse_settings_profiles + - select + - SQL_driven + +- name: CLICKHOUSE | Drop settings-profiles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP SETTINGS PROFILE IF EXISTS {{ item }} ;\"" + with_items: "{{ clickhouse_existing_sql_settings_profiles.stdout_lines }}" + when: + - clickhouse_existing_sql_settings_profiles is defined + - item not in clickhouse_custom_settings_profiles | map(attribute='name') | list + no_log: True + register: drop_profiles + changed_when: drop_profiles.rc != 0 + tags: + - clickhouse_settings_profiles + - delete + - SQL_driven + +- name: CLICKHOUSE | Add settings profiles (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/SETTINGS-PROFILE.j2') | quote }}" + with_items: "{{ clickhouse_custom_settings_profiles }}" + when: + - clickhouse_custom_settings_profiles is defined + - item.name not in clickhouse_existing_sql_settings_profiles.stdout_lines + no_log: True + register: add_profiles + changed_when: add_profiles.rc != 0 + tags: + - clickhouse_settings_profiles + - create + - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/users.yml b/ansible/roles/clickhouse/tasks/config/users.yml new file mode 100644 index 00000000..71c305e5 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/config/users.yml @@ -0,0 +1,39 @@ +--- + +- name: CLICKHOUSE | List users (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.users WHERE storage == 'local directory';\"" + changed_when: False + register: clickhouse_existing_sql_users + no_log: True + tags: + - clickhouse_users + - select + - SQL_driven + +- name: CLICKHOUSE | Drop users (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP USER IF EXISTS '{{ item }}' ;\"" + with_items: "{{ clickhouse_existing_sql_users.stdout_lines }}" + when: + - clickhouse_existing_sql_users is defined + - item not in clickhouse_custom_users | map(attribute='name') | list + no_log: True + register: drop_users + changed_when: drop_users.rc != 0 + tags: + - clickhouse_users + - delete + - SQL_driven + +- name: CLICKHOUSE | Add users (SQL-driven) + ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/USER.j2') | quote }}" + with_items: "{{ clickhouse_custom_users }}" + when: + - clickhouse_custom_users is defined + - item.name not in clickhouse_existing_sql_users.stdout_lines + no_log: True + register: add_users + changed_when: add_users.rc != 0 + tags: + - clickhouse_users + - create + - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/install-Debian.yml b/ansible/roles/clickhouse/tasks/install-Debian.yml new file mode 100644 index 00000000..47318a80 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/install-Debian.yml @@ -0,0 +1,108 @@ +--- + +- name: CLICKHOUSE | Ensure clickhouse group + ansible.builtin.group: + name: "{{ clickhouse_group }}" + system: yes + state: present + tags: [clickhouse_install] + +- name: CLICKHOUSE | Ensure clickhouse user + ansible.builtin.user: + name: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + system: yes + shell: /usr/sbin/nologin + createhome: no + tags: [clickhouse_install] + +- name: CLICKHOUSE | Ensure base directory exist + ansible.builtin.file: + state: directory + dest: "{{ clickhouse_base_path }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: "{{ clickhouse_base_directory_mode }}" + become: true + tags: [clickhouse_install] + +- name: CLICKHOUSE | Update repositories cache and install apt-transport-https, dirmngr ansible.builtin.packages + apt: + name: "{{ clickhouse_prerequisite_repos }}" + state: present + tags: [clickhouse_install] + +- name: CLICKHOUSE | Be sure key is present + ansible.builtin.apt_key: + keyserver: "{{ clickhouse_deb_keyserver }}" + id: "{{ clickhouse_deb_keyid }}" + state: present + tags: [clickhouse_install] + +- name: CLICKHOUSE | Add clickhouse apt repository (deb) + ansible.builtin.apt_repository: + repo: "{{ clickhouse_deb_repo }}" + state: present + tags: [clickhouse_install] + +- name: CLICKHOUSE | Be sure clickhouse packages are installed (apt) + ansible.builtin.apt: + name: "{{ clickhouse_packages }}" + state: present + update_cache: true + tags: [clickhouse_install] + +- name: CLICKHOUSE | Ensure user files and access control directories exist + ansible.builtin.file: + state: directory + dest: "{{ item }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: 0750 + with_items: + - "{{ clickhouse_access_control_directory }}" + - "{{ clickhouse_user_files_directory }}" + become: true + tags: [clickhouse_install] + +- name: CLICKHOUSE | Ensure needed config directories exist + ansible.builtin.file: + state: directory + dest: "{{ item }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: "{{ clickhouse_config_directory_mode }}" + with_items: + - "{{ clickhouse_config_directory }}" + - "{{ clickhouse_config_directory_config_d }}" + - "{{ clickhouse_config_directory_users_d }}" + become: true + tags: [clickhouse_install] + +- name: CLICKHOUSE | Ensure needed tmp directories exist + ansible.builtin.file: + state: directory + dest: "{{ clickhouse_tmp_directory }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: "{{ clickhouse_tmp_directory_mode }}" + become: true + tags: [clickhouse_install] + +- name: CLICKHOUSE | Ensure needed log directory exist + ansible.builtin.file: + state: directory + dest: "{{ clickhouse_log_directory }}" + owner: "{{ clickhouse_user }}" + group: "{{ clickhouse_group }}" + mode: "{{ clickhouse_log_directory_mode }}" + become: true + tags: [clickhouse_install] + +- name: CLICKHOUSE | Clickhouse extra packages are installed (apt) + ansible.builtin.apt: + name: "{{ clickhouse_packages_extra }}" + state: present + update_cache: true + when: clickhouse_packages_extra is defined + tags: [clickhouse_install] diff --git a/ansible/roles/clickhouse/tasks/main.yml b/ansible/roles/clickhouse/tasks/main.yml new file mode 100644 index 00000000..dbd9322e --- /dev/null +++ b/ansible/roles/clickhouse/tasks/main.yml @@ -0,0 +1,79 @@ +--- + +- name: CLICKHOUSE | Install + ansible.builtin.include_tasks: "install-{{ ansible_os_family | capitalize }}.yml" + tags: + - clickhouse_install + when: clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | Configure clickhouse + ansible.builtin.include_tasks: config/clickhouse.yml + tags: + - clickhouse_configure + when: clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | Configure copier + ansible.builtin.include_tasks: config/copier.yml + tags: + - clickhouse_configure_copier + when: clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | ClickHouse Service + ansible.builtin.include_tasks: service/clickhouse.yml + tags: + - clickhouse_service + when: clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | JDBC Bridge Service + ansible.builtin.include_tasks: service/jdbc-bridge-{{ ansible_distribution }}.yml + tags: + - clickhouse_jdbc_bridge_service + when: clickhouse_install_bridge + +- name: CLICKHOUSE | Configure settings profiles + ansible.builtin.include_tasks: config/settings-profiles.yml + tags: + - clickhouse_settings_profiles + when: + - clickhouse_role_manage_settings_profiles + - clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | Configure roles + ansible.builtin.include_tasks: config/roles.yml + tags: + - clickhouse_roles + when: + - clickhouse_role_manage_roles + - clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | Configure quotas + ansible.builtin.include_tasks: config/quotas.yml + tags: + - clickhouse_quotas + when: + - clickhouse_role_manage_quotas + - clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | Configure users + ansible.builtin.include_tasks: config/users.yml + tags: + - clickhouse_users + when: + - clickhouse_role_manage_users + - clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | Configure databases + ansible.builtin.include_tasks: config/databases.yml + tags: + - clickhouse_databases + when: + - clickhouse_role_manage_databases + - clickhouse_install_db is not defined or clickhouse_install_db + +- name: CLICKHOUSE | Configure perms & privs + ansible.builtin.include_tasks: config/perms_and_privs.yml + tags: + - clickhouse_grants + when: + - clickhouse_role_manage_grants + - clickhouse_install_db is not defined or clickhouse_install_db diff --git a/ansible/roles/clickhouse/tasks/service/clickhouse.yml b/ansible/roles/clickhouse/tasks/service/clickhouse.yml new file mode 100644 index 00000000..6f1afc15 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/service/clickhouse.yml @@ -0,0 +1,28 @@ +--- + +- name: CLICKHOUSE | Copy systemd unit configuration file + ansible.builtin.template: + src: "{{ clickhouse_service_template_path }}" + dest: "/etc/systemd/system/{{ clickhouse_service }}.service" + mode: 0644 + owner: root + group: root + notify: Reload-clickhouse + +- name: CLICKHOUSE | Configuring service + ansible.builtin.systemd: + name: "{{ clickhouse_service }}" + state: "{{ clickhouse_service_state }}" + enabled: "{{ clickhouse_service_enabled }}" + daemon_reload: yes + +- name: CLICKHOUSE | Wait for service listening + ansible.builtin.wait_for: + host: localhost + port: "{{ clickhouse_http_port }}" + state: "{{ clickhouse_service_state }}" + delay: 5 + timeout: 300 + when: + - clickhouse_service_enabled + - clickhouse_service_state == "started" diff --git a/ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml b/ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml new file mode 100644 index 00000000..debb5bb1 --- /dev/null +++ b/ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml @@ -0,0 +1,103 @@ +--- + +- name: CLICKHOUSE | Ensure clickhouse jdbc bridge group + ansible.builtin.group: + name: "{{ clickhouse_jdbc_bridge_group }}" + system: yes + state: present + +- name: CLICKHOUSE | Ensure clickhouse jdbc bridge user + ansible.builtin.user: + name: "{{ clickhouse_jdbc_bridge_user }}" + group: "{{ clickhouse_jdbc_bridge_group }}" + system: yes + shell: /usr/sbin/nologin + createhome: no + +- name: CLICKHOUSE | Ensure needed jdbc-bridge directories exist + ansible.builtin.file: + state: directory + dest: "{{ item }}" + owner: "{{ clickhouse_jdbc_bridge_user }}" + group: "{{ clickhouse_jdbc_bridge_group }}" + mode: 0750 + with_items: + - "{{ clickhouse_jdbc_bridge_config_path }}" + - "{{ clickhouse_jdbc_bridge_drivers_path }}" + - "{{ clickhouse_jdbc_bridge_datasources_path }}" + - "{{ clickhouse_jdbc_bridge_log_path }}" + become: true + tags: + - clickhouse_jdbc_bridge_service + +- name: CLICKHOUSE | Install Clickhouse JDBC Bridge if required + ansible.builtin.apt: + deb: "{{ clickhouse_jdbc_bridge_deb_package }}" + install_recommends: no + tags: + - clickhouse_jdbc_bridge_service + +- name: "CLICKHOUSE | Copy Clickhouse JDBC Bridge drivers from {{ clickhouse_jdbc_bridge_file_drivers_path }}" + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ clickhouse_jdbc_bridge_drivers_path }}" + owner: "{{ clickhouse_jdbc_bridge_user }}" + group: "{{ clickhouse_jdbc_bridge_group }}" + mode: 0640 + with_fileglob: + - "{{ clickhouse_jdbc_bridge_file_drivers_path }}/*" + tags: + - clickhouse_jdbc_bridge_service + +- name: CLICKHOUSE | Generate JDBC Bridge templates for datasources + ansible.builtin.template: + src: "jdbc-bridge/datasource.json.j2" + dest: "{{ clickhouse_jdbc_bridge_datasources_path }}/{{ item.name }}.json" + owner: "{{ clickhouse_jdbc_bridge_user }}" + group: "{{ clickhouse_jdbc_bridge_group }}" + mode: 0644 + with_items: "{{ clickhouse_jdbc_bridge_datasources }}" + become: true + notify: "{{ clickhouse_jdbc_bridge_handler_on_config_change }}" + tags: + - clickhouse_jdbc_bridge_service + +- name: CLICKHOUSE | Copy Clickhouse JDBC Bridge logging properties + ansible.builtin.template: + src: "jdbc-bridge/logging.properties.j2" + dest: "{{ clickhouse_jdbc_bridge_config_path }}/logging.properties" + owner: "{{ clickhouse_jdbc_bridge_user }}" + group: "{{ clickhouse_jdbc_bridge_group }}" + mode: 0644 + tags: + - clickhouse_jdbc_bridge_service + +- name: CLICKHOUSE | Copy Clickhouse JDBC Bridge env file + ansible.builtin.template: + src: "jdbc-bridge/set-env.sh.j2" + dest: "{{ clickhouse_jdbc_bridge_config_path }}/set-env.sh" + owner: "{{ clickhouse_jdbc_bridge_user }}" + group: "{{ clickhouse_jdbc_bridge_group }}" + mode: 0744 + tags: + - clickhouse_jdbc_bridge_service + +- name: CLICKHOUSE | Copy Clickhouse JDBC Bridge service + ansible.builtin.template: + src: "{{ clickhouse_jdbc_bridge_service_template_path }}" + dest: "/etc/systemd/system/{{ clickhouse_jdbc_bridge_service }}.service" + mode: 0644 + owner: root + group: root + tags: + - clickhouse_jdbc_bridge_service + +- name: CLICKHOUSE | Configuring Clickhouse JDBC Bridge service + ansible.builtin.systemd: + name: "{{ clickhouse_jdbc_bridge_service }}" + state: "{{ clickhouse_jdbc_bridge_service_state }}" + enabled: "{{ clickhouse_jdbc_bridge_service_enabled }}" + daemon_reload: yes + notify: "{{ clickhouse_jdbc_bridge_handler_on_config_change }}" + tags: + - clickhouse_jdbc_bridge_service diff --git a/ansible/roles/clickhouse/templates/DDL/DATABASE.j2 b/ansible/roles/clickhouse/templates/DDL/DATABASE.j2 new file mode 100644 index 00000000..db58eab1 --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/DATABASE.j2 @@ -0,0 +1,5 @@ +CREATE DATABASE IF NOT EXISTS {{ item.name }}{% if item.cluster is defined %} ON CLUSTER {{item.cluster }}{% endif %} +{% if item.engine is defined %} ENGINE = {{item.engine }}{% endif %} +{% if item.comment is defined %} COMMENT '{{item.comment }}'{% endif %} +; +{# https://clickhouse.com/docs/en/sql-reference/statements/create/role/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/GRANT.j2 b/ansible/roles/clickhouse/templates/DDL/GRANT.j2 new file mode 100644 index 00000000..f7d339d6 --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/GRANT.j2 @@ -0,0 +1,4 @@ +{% for database in item.databases %}{% for table in item.tables %} +GRANT {% if item.cluster is defined %}ON CLUSTER {{ item.cluster }} {% endif %}{% for privilege in item.privileges %}{{ privilege }}{% if item.columns is defined %}({{ item.columns | join(', ') }}){% endif %}{{ ", " if not loop.last else "" }}{% endfor %} ON {{ database }}.{{ table }} TO {{ item.to | join(', ') }}{% if item.with_grant_option is defined and item.with_grant_option|bool %} WITH GRANT OPTION{% endif %}{% if item.with_replace_option is defined and item.with_replace_option|bool %} WITH REPLACE OPTION{% endif %}; +{% endfor %}{% endfor %} +{# https://clickhouse.com/docs/en/sql-reference/statements/grant #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 b/ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 new file mode 100644 index 00000000..edfae953 --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 @@ -0,0 +1,2 @@ +GRANT {% if item.cluster is defined %}ON CLUSTER {{ item.cluster }} {% endif %}{{ item.roles | join(', ') }} TO {{ item.to | join(', ') }}{% if item.with_admin_option is defined and item.with_admin_option|bool %} WITH ADMIN OPTION{% endif %}{% if item.with_replace_option is defined and item.with_replace_option|bool %} WITH REPLACE OPTION{% endif %}; +{# https://clickhouse.com/docs/en/sql-reference/statements/grant #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/QUOTA.j2 b/ansible/roles/clickhouse/templates/DDL/QUOTA.j2 new file mode 100644 index 00000000..5b5a5f13 --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/QUOTA.j2 @@ -0,0 +1,7 @@ +CREATE QUOTA IF NOT EXISTS {{ item.name }}{% if item.cluster is defined %} ON CLUSTER {{item.cluster }}{% endif %} +{% if item.keyed is defined %}{% if item.keyed == 'NOT KEYED' %} {{ item.keyed }}{% else %} KEYED BY {{ item.keyed }}{% endif %}{% endif %} +{% if item.settings is defined %} FOR {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +{% if item.to is defined %} TO {% if item.to == 'ALL' %}{{ item.to }}{% elif item.to[0] is match('^[\-]') %}ALL EXCEPT {% for to in item.to %}{{ to | regex_replace('^[\-]', '') }}{{ ", " if not loop.last else "" }}{% endfor %} +{% else %}{% for to in item.to %}{{ to }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} {% endif %} +; +{# https://clickhouse.com/docs/en/sql-reference/statements/create/quota/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/REVOKE.j2 b/ansible/roles/clickhouse/templates/DDL/REVOKE.j2 new file mode 100644 index 00000000..c6f6f00b --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/REVOKE.j2 @@ -0,0 +1,25 @@ +{% for grant in actual_privs %} +{% for priv in grant.access_type %} +{% set revoke_condition = +(grant.user_name in item.to) or (grant.role_name in item.to) +and grant.database in item.databases +and grant.table in item.tables +and grant.column in item.columns|default('*') +and priv not in item.privileges +and grant.grant_option|bool != item.with_grant_option|default(False) +-%} +{% if revoke_condition %} +REVOKE {{ priv }}{% if item.columns is defined %}({{ item.columns | join(', ') }}){% endif %} ON {{ grant.database }}.{{ grant.table }} FROM {{ grant.user_name }}; +{% endif %} +{% if grant.grant_option|bool and not item.with_grant_option|default(False) %} +REVOKE GRANT OPTION FOR {{ priv }} ON {{ grant.database }}.{{ grant.table }} FROM {{ grant.user_name }}; +{% endif %} +{% endfor %} +{% endfor %} +{# --------------- #} +{# REVOKE {{ row.1 }}{% if item.columns is defined %}({{ item.columns | join(', ') }}){% endif %} ON {{ row.0.database }}.{{ row.0.table }} FROM {{ row.0.user_name }}; + +{% if row.0.grant_option == 'True' and item.with_grant_option == 'False' %} +REVOKE GRANT OPTION FOR {{ row.1 }} ON {{ row.0.database }}.{{ row.0.table }} FROM {{ row.0.user_name }}; +{% endif %} #} +{# https://clickhouse.com/docs/en/sql-reference/statements/grant #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/ROLE.j2 b/ansible/roles/clickhouse/templates/DDL/ROLE.j2 new file mode 100644 index 00000000..f58223da --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/ROLE.j2 @@ -0,0 +1,5 @@ +CREATE ROLE IF NOT EXISTS {{ item.name }} +{% if item.settings is defined %} SETTINGS {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +{% if item.profile is defined %} SETTINGS PROFILE {% for profile in item.profile %}'{{ profile }}'{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +; +{# https://clickhouse.com/docs/en/sql-reference/statements/create/role/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 b/ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 new file mode 100644 index 00000000..93bd4637 --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 @@ -0,0 +1,4 @@ +CREATE SETTINGS PROFILE IF NOT EXISTS {{ item.name }} +{% if item.settings is defined %} SETTINGS {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +; +{# https://clickhouse.com/docs/en/sql-reference/statements/create/settings-profile/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/USER.j2 b/ansible/roles/clickhouse/templates/DDL/USER.j2 new file mode 100644 index 00000000..d2fe3e5e --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/USER.j2 @@ -0,0 +1,10 @@ +CREATE USER IF NOT EXISTS '{{ item.name }}'{% if item.cluster is defined %} ON CLUSTER {{item.cluster }}{% endif %} +{% if item.ldap_server is defined %} IDENTIFIED WITH ldap SERVER '{{ item.ldap_server }}'{% elif item.kerberos is defined %} IDENTIFIED WITH kerberos {% if item.kerberos != '' %}REALM '{{ item.kerberos }}'{% endif %}{% elif item.password is defined %} IDENTIFIED WITH {{ item.password_type | default("sha256_password") | lower }}{% if item.password is defined and item.password_type is not defined or (item.password is defined and item.password_type | upper != 'NO_PASSWORD') %} BY '{{ item.password}}'{% endif %}{% else %} NOT IDENTIFIED{% endif %} +{% if item.networks is defined %} HOST {% for host in item.networks %}{{ host }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +{% if item.role is defined %} DEFAULT ROLE {% for role in item.role %}{{ role }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +{% if item.default_database is defined %}DEFAULT DATABASE {{ item.default_database }}{% endif %} +{% if item.grantees is defined %} GRANTEES {% for grantee in item.grantees %}{% if grantee is not match('^[\-]') %}{{ ", " if not loop.first and grantee is not match('^[\-]') else "" }}{{ grantee }}{% endif %}{% endfor %}{% if item.grantees | map('regex_search', '^[\-]') | select('string') | list| length > 0 %} EXCEPT {% endif %}{% for exception in item.grantees | map('regex_search', '^[\-].*') | select('string') | list %}{{ exception | regex_replace('^[\-]', '')}}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +{% if item.settings is defined %} SETTINGS {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +{% if item.profile is defined %} SETTINGS PROFILE {% for profile in item.profile %}'{{ profile }}'{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} +; +{# https://clickhouse.com/docs/en/sql-reference/statements/create/user/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/clickhouse-server.service.j2 b/ansible/roles/clickhouse/templates/clickhouse-server.service.j2 new file mode 100644 index 00000000..7d366bed --- /dev/null +++ b/ansible/roles/clickhouse/templates/clickhouse-server.service.j2 @@ -0,0 +1,32 @@ +{{ ansible_managed | comment }} +[Unit] +Description=ClickHouse Server (analytic DBMS for big data) +Documentation= {{ clickhouse_documentation_link }} +Requires=network-online.target +# NOTE: that After/Wants=time-sync.target is not enough, you need to ensure +# that the time was adjusted already, if you use systemd-timesyncd you are +# safe, but if you use ntp or some other daemon, you should configure it +# additionaly. +After=time-sync.target network-online.target +Wants=time-sync.target + +[Service] +Type=simple +User={{ clickhouse_user }} +Group={{ clickhouse_group }} +Restart=always +RestartSec=30 +PrivateTmp= {{ clickhouse_private_tmp }} +RuntimeDirectory={{ clickhouse_service }} +ExecStart=/usr/bin/{{ clickhouse_service }} --config={{ clickhouse_config_directory }}/{{ clickhouse_config_file_name }} --pid-file=/run/clickhouse-server/{{ clickhouse_service }}.pid +ExecReload=/bin/kill -HUP $MAINPID +EnvironmentFile=-{{ clickhouse_environment_file }} +LimitCORE=infinity +LimitNOFILE={{ clickhouse_limit_no_file }} +CapabilityBoundingSet=CAP_NET_ADMIN CAP_IPC_LOCK CAP_SYS_NICE CAP_NET_BIND_SERVICE + +StandardOutput={{ clickhouse_log_output }} +StandardError={{ clickhouse_log_output }} + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/clickhouse/templates/config.xml.j2 b/ansible/roles/clickhouse/templates/config.xml.j2 new file mode 100644 index 00000000..ed6f950b --- /dev/null +++ b/ansible/roles/clickhouse/templates/config.xml.j2 @@ -0,0 +1,1605 @@ + + + + + + {{ clickhouse_logger.level }} + {{ clickhouse_logger.log }} + {{ clickhouse_logger.errorlog }} + + {{ clickhouse_logger.size }} + {{ clickhouse_logger.count }} + {% if clickhouse_logger.console is defined and clickhouse_logger.console != 'autodetection' -%} + {{ clickhouse_logger.console }} + {% else -%} + + {% endif -%} + + + + + {% if clickhouse_logger_overrides_legacy is defined %}{{ clickhouse_logger_overrides_legacy }}{% endif %} + + + + {% if clickhouse_logger_overrides is defined %}{{ clickhouse_logger_overrides }}{% endif %} + + + + + + {% if clickhouse_custom_headers_response is defined %}{{ clickhouse_custom_headers_response }}{% endif %} + + + + + + {{ clickhouse_http_port }} + + + {{ clickhouse_tcp_port }} + + + {{ clickhouse_mysql_port }} + + + {{ clickhouse_postgresql_port }} + + + + {% if clickhouse_https_port is defined -%} + {{ clickhouse_https_port }} + {% endif -%} + + + + {% if clickhouse_tcp_port_secure is defined -%} + {{ clickhouse_tcp_port_secure }} + {% endif -%} + + + + {% if clickhouse_tcp_with_proxy_port is defined -%} + {{ clickhouse_tcp_with_proxy_port }} + {% endif -%} + + + {{ clickhouse_interserver_http_port }} + + + + {% if clickhouse_interserver_https_port is defined -%} + {{ clickhouse_interserver_https_port }} + {% endif -%} + + + + {% if clickhouse_interserver_http_host is defined -%} + {{ clickhouse_interserver_http_host }} + {% endif -%} + + + + {% if clikhouse_interserver_http_credentials is defined -%} + + {{ clikhouse_interserver_http_credentials.user }} + {{ clikhouse_interserver_http_credentials.password }} + + {% endif -%} + + + + + + + + + + {% if clickhouse_listen_hosts is defined -%} + {% for host in clickhouse_listen_hosts -%} + {{ host }} + {% endfor -%} + {% endif -%} + + + + {% if clickhouse_listen_try is defined -%} + {{ clickhouse_listen_try }} + {% endif -%} + + + + {% if clickhouse_listen_reuse_port is defined -%} + {{ clickhouse_listen_reuse_port }} + {% endif -%} + + + {% if clickhouse_listen_backlog is defined -%} + {{ clickhouse_listen_backlog }} + {% endif -%} + + {{ clickhouse_config.max_connections }} + + + {{ clickhouse_config.keep_alive_timeout }} + + + + {% if clickhouse_grpc_port is defined -%} + {{ clickhouse_grpc_port }} + {% endif -%} + + {% if clickhouse_grpc_port is defined -%} + {{ clickhouse_grpc_port }} + {% endif -%} + {{ clickhouse_enable_ssl }} + + + {% if clickhouse_enable_ssl is true -%} + {{ clickhouse_ssl_cert_file }} + {{ clickhouse_ssl_key_file }} + {% endif -%} + + + {{ clickhouse_ssl_require_client_auth }} + + + {{ clickhouse_ssl_ca_cert_file }} + + + {{ clickhouse_grpc_compression }} + + + {{ clickhouse_grpc_compression_level }} + + + {{ clickhouse_max_send_message_size }} + {{ clickhouse_max_receive_message_size }} + + + {{ clickhouse_verbose_logs }} + + + + + + + {{ clickhouse_certificatefile }} + {{ clickhouse_privatekeyfile }} + + {{ clickhouse_dhparamsfile }} + {{ clickhouse_verificationmode }} + {{ clickhouse_loaddefaultcafile_server }} + {{ clickhouse_cachesessions_server }} + {{ clickhouse_disableprotocols_server }} + {{ clickhouse_preferserverciphers_server }} + + + + {{ clickhouse_loaddefaultcafile_client }} + {{ clickhouse_cachesessions_client }} + {{ clickhouse_disableprotocols_client }} + {{ clickhouse_preferserverciphers_client }} + + + + {{ clickhouse_invalidcertificatehandler.name }} + + + + + + + {% if clickhouse_http_server_default_response is defined -%} + {{ clickhouse_http_server_default_response }} + {% endif -%} + + + {{ clickhouse_config.max_concurrent_queries }} + + + {{ clickhouse_config.max_server_memory_usage }} + + + {{ clickhouse_config.max_thread_pool_size }} + + + + + + {{ clickhouse_config.max_server_memory_usage_to_ram_ratio }} + + + {{ clickhouse_config.total_memory_profiler_step }} + + + {{ clickhouse_config.total_memory_tracker_sample_probability }} + + + + {% if clickhouse_config.max_open_files is defined -%} + {{ clickhouse_config.max_open_files }} + {% endif -%} + + + {{ clickhouse_config.uncompressed_cache_size }} + + + {{ clickhouse_config.mark_cache_size }} + + + + {{ clickhouse_config.mmap_cache_size }} + + + {{ clickhouse_config.compiled_expression_cache_size }} + + + {{ clickhouse_config.compiled_expression_cache_elements_size }} + + + {{ clickhouse_base_path }} + + + {{ clickhouse_tmp_directory }} + + + + {% if clickhouse_config.tmp_policy is defined -%} + {{ clickhouse_config.tmp_policy }} + {% endif -%} + + + {{ clickhouse_user_files_directory }} + + + + + {% if clickhouse_ldap is defined -%} + {% for ldap_server in clickhouse_ldap %} + <{{ ldap_server.servername }}> + {% if ldap_server.host is defined -%}{{ ldap_server.host }}{% endif -%} + {% if ldap_server.port is defined -%}{{ ldap_server.port }}{% endif -%} + {% if ldap_server.bind_dn is defined -%}{{ ldap_server.bind_dn }}{% endif -%} + {% if ldap_server.base_dn is defined or ldap_server.scope or ldap_server.search_filter -%} + {% if clickhouse_auth_backend == 'AD' -%}{% endif -%} + {% if ldap_server.base_dn is defined -%}{{ ldap_server.base_dn }}{% endif -%} + {% if ldap_server.scope is defined -%}{{ ldap_server.scope }}{% endif -%} + {% if ldap_server.search_filter is defined -%}{{ ldap_server.search_filter }}{% endif -%} + {% if clickhouse_auth_backend == 'AD' -%}{% endif -%} + {% endif -%} + {% if ldap_server.verification_cooldown is defined -%}{{ ldap_server.verification_cooldown }}{% endif -%} + {% if ldap_server.enable_tls is defined -%}{{ ldap_server.enable_tls }}{% endif -%} + {% if ldap_server.tls_minimum_protocol_version is defined -%}{{ ldap_server.tls_minimum_protocol_version }}{% endif -%} + {% if ldap_server.tls_require_cert is defined -%}{{ ldap_server.tls_require_cert }}{% endif -%} + {% if ldap_server.tls_cert_file is defined -%}{{ ldap_server.tls_cert_file }}{% endif -%} + {% if ldap_server.tls_key_file is defined -%}{{ ldap_server.tls_key_file }}{% endif -%} + {% if ldap_server.tls_ca_cert_file is defined -%}{{ ldap_server.tls_ca_cert_file }}{% endif -%} + {% if ldap_server.tls_ca_cert_dir is defined -%}{{ ldap_server.tls_ca_cert_dir }}{% endif -%} + {% if ldap_server.tls_cipher_suite is defined -%}{{ ldap_server.tls_cipher_suite }}{% endif -%} + + {% endfor %} + {% endif -%} + + + + {% if clickhouse_kerberos is defined -%} + + {% if clickhouse_kerberos.principal is defined%}{{ clickhouse_kerberos.principal }}{% endif -%} + {% if clickhouse_kerberos.realm is defined%}{{ clickhouse_kerberos.realm }}{% endif -%} + + {% endif -%} + + + + + + {{ clickhouse_users_file_name }} + + + + {{ clickhouse_access_control_directory }} + + + + {% if clickhouse_user_directories_ldap is defined -%} + {% for user_directory in clickhouse_user_directories_ldap -%} + + {{ user_directory.server }} + {% if user_directory.roles is defined -%} + + {% for role in user_directory.roles -%} + <{{ role }} /> + {% endfor -%} + + {% endif -%} + {% if user_directory.role_mappings is defined -%} + {% for role_mapping in user_directory.role_mappings -%} + + {{ role_mapping.base_dn }} + {{ role_mapping.scope }} + {{ role_mapping.search_filter }} + {{ role_mapping.attribute }} + {{ role_mapping.prefix }} + + {% endfor -%} + {% endif -%} + + {% endfor -%} + {% endif -%} + + + + {{ clickhouse_config.default_profile }} + + + {{ clickhouse_config.custom_settings_prefixes }} + + + + {% if clickhouse_config.system_profile is defined -%} + {{ clickhouse_config.system_profile }} + {% endif -%} + + + + {% if clickhouse_config.buffer_profile is defined -%} + {{ clickhouse_config.buffer_profile }} + {% endif -%} + + + {{ clickhouse_config.default_database }} + + + + {% if clickhouse_config.timezone is defined -%} + {{ clickhouse_config.timezone }} + {% endif -%} + + + + {% if clickhouse_config.umask is defined -%} + {{ clickhouse_config.umask }} + {% endif -%} + + {{ clickhouse_config.mlock_executable }} + + + {{ clickhouse_config.remap_executable }} + + + {% if clickhouse_jdbc_bridge is defined -%} + + {{ clickhouse_jdbc_bridge.host }} + {{ clickhouse_jdbc_bridge.port }} + + {% endif -%} + + +{% if clickhouse_remote_servers is defined %} + +{% for server in clickhouse_remote_servers %} + <{{ server.servername }}> +{% if server.secret is defined %} + {{ server.secret }} +{% endif %} +{% for shard in server.shards %} + +{% if shard.internal_replication is defined %} + {{ shard.internal_replication }} +{% endif %} +{% if shard.weight is defined %} + {{ shard.weight }} +{% endif %} +{% for replica in shard.replicas %} + + {{ replica.host }} + {{ replica.port }} +{% if replica.user is defined %} + {{ replica.user }} +{% endif %} +{% if replica.password is defined %} + {{ replica.password }} +{% endif %} +{% if replica.priority is defined %} + {{ replica.priority }} +{% endif %} +{% if replica.secure is defined %} + {{ replica.secure }} +{% endif %} +{% if replica.compression is defined %} + {{ replica.compression }} +{% endif %} + +{% endfor %} + +{% endfor %} + +{% endfor %} + +{% endif %} + + + + + + + +{% if clickhouse_remote_url_allow_hosts is defined %} + +{% for host in clickhouse_remote_url_allow_hosts %} + {{ host }} +{% endfor %} + +{% endif %} + + + + +{% if clickhouse_keeper is defined %} +{% for keeper_server in clickhouse_keeper.keeper_servers %} +{% if keeper_server.server is defined and inventory_hostname == keeper_server.server %} + + + {{ clickhouse_keeper.tcp_port }} + {{ keeper_server.id }} + {{ clickhouse_keeper.log_storage_path }} + {{ clickhouse_keeper.snapshot_storage_path }} + + + {{ clickhouse_keeper.coordination_settings.operation_timeout_ms }} + {{ clickhouse_keeper.coordination_settings.session_timeout_ms }} + {{ clickhouse_keeper.coordination_settings.raft_logs_level }} + + + +{% for keeper_server in clickhouse_keeper.keeper_servers %} + + {{ keeper_server.id }} + {{ keeper_server.hostname }} + {{ keeper_server.port }} + +{% endfor %} + + +{% endif %} +{% endfor %} +{% endif %} + + + + +{% if clickhouse_zookeeper is defined %} + +{% for node in clickhouse_zookeeper %} + + {{ node.host }} + {{ node.port }} + +{% endfor %} + +{% endif %} + + + + +{% if clickhouse_macros is defined %} + +{% for macros in clickhouse_macros %} +{% if macros.server is defined and inventory_hostname in macros.server %}{{ macros.macro|indent(4, true) }}{% endif %} +{% if macros.server is not defined %}{{ macros.macro|indent(4, true) }}{% endif %} +{% endfor %} + +{# Deprecated, please use clickhouse_macros instead of clickhouse_replicated_tables_macros #} +{% elif clickhouse_replicated_tables_macros is defined %} + +{% for macros in clickhouse_replicated_tables_macros %} +{% if macros.server == inventory_hostname %}{{ macros.macro|indent(4, true) }}{% endif %} +{% endfor %} + +{% endif %} + + + {{ clickhouse_config.builtin_dictionaries_reload_interval }} + + + {{ clickhouse_config.max_session_timeout }} + + + {{ clickhouse_config.default_session_timeout }} + + + + +{% if clickhouse_graphite is defined %} +{% for graphite in clickhouse_graphite %} + + {{ graphite.host }} + {{ graphite.port }} + {{ graphite.timeout }} +{% if graphite.interval is defined %} + {{ graphite.interval }} +{% endif %} +{% if graphite.root_path is defined %} + {{ graphite.root_path }} +{% endif %} +{% if graphite.hostname_in_path is defined %} + {{ graphite.hostname_in_path }} +{% endif %} +{% if graphite.metrics is defined %} + {{ graphite.metrics }} +{% endif %} +{% if graphite.events is defined %} + {{ graphite.events }} +{% endif %} +{% if graphite.events_cumulative is defined %} + {{ graphite.events_cumulative }} +{% endif %} +{% if graphite.asynchronous_metrics is defined %} + {{ graphite.asynchronous_metrics }} +{% endif %} + +{% endfor %} +{% endif %} + + + + +{% if clickhouse_prometheus is defined %} + + {{ clickhouse_prometheus.endpoint }} + {{ clickhouse_prometheus.port }} + {{ clickhouse_prometheus.metrics }} + {{ clickhouse_prometheus.events }} + {{ clickhouse_prometheus.asynchronous_metrics }} + {{ clickhouse_prometheus.status_info }} + +{% endif %} + + + + + {{ clickhouse_tables_log.query_log.database }} + {{ clickhouse_tables_log.query_log.table }}
+ +{% if clickhouse_tables_log.query_log.partition_by is defined %} + {{ clickhouse_tables_log.query_log.partition_by }} +{% endif %} + +{% if clickhouse_tables_log.query_log.ttl is defined %} + {{ clickhouse_tables_log.query_log.ttl }} +{% endif %} + + +{% if clickhouse_tables_log.query_log.engine is defined %} + {{ clickhouse_tables_log.query_log.engine }} +{% endif %} + + + {{ clickhouse_tables_log.query_log.flush_interval_milliseconds }} +
+ + + + {{ clickhouse_tables_log.trace_log.database }} + {{ clickhouse_tables_log.trace_log.table }}
+ {{ clickhouse_tables_log.trace_log.partition_by }} + {{ clickhouse_tables_log.trace_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.trace_log.ttl is defined %} + {{ clickhouse_tables_log.trace_log.ttl }} +{% endif %} +
+ + + + {{ clickhouse_tables_log.thread_log.database }} + {{ clickhouse_tables_log.thread_log.table }}
+ {{ clickhouse_tables_log.thread_log.partition_by }} + {{ clickhouse_tables_log.thread_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.thread_log.ttl is defined %} + {{ clickhouse_tables_log.thread_log.ttl }} +{% endif %} +
+ + + + {{ clickhouse_tables_log.views_log.database }} + {{ clickhouse_tables_log.views_log.table }}
+ {{ clickhouse_tables_log.views_log.partition_by }} + {{ clickhouse_tables_log.views_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.views_log.ttl is defined %} + {{ clickhouse_tables_log.views_log.ttl }} +{% endif %} +
+ + + + {{ clickhouse_tables_log.part_log.database }} + {{ clickhouse_tables_log.part_log.table }}
+ {{ clickhouse_tables_log.part_log.partition_by }} + {{ clickhouse_tables_log.part_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.part_log.ttl is defined %} + {{ clickhouse_tables_log.part_log.ttl }} +{% endif %} +
+ + +{% if clickhouse_tables_log.text_log is defined %} + + {{ clickhouse_tables_log.text_log.database }} + {{ clickhouse_tables_log.text_log.table }}
+ {{ clickhouse_tables_log.text_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.text_log.ttl is defined %} + {{ clickhouse_tables_log.text_log.ttl }} +{% endif %} +{% if clickhouse_tables_log.text_log.level is defined %} + clickhouse_tables_log.text_log.level +{% endif %} +
+{% endif %} + + + + {{ clickhouse_tables_log.metric_log.database }} + {{ clickhouse_tables_log.metric_log.table }}
+ {{ clickhouse_tables_log.metric_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.metric_log.ttl is defined %} + {{ clickhouse_tables_log.metric_log.ttl }} +{% endif %} + {{ clickhouse_tables_log.metric_log.collect_interval_milliseconds }} +
+ + + + {{ clickhouse_tables_log.asynchronous_metric_log.database }} + {{ clickhouse_tables_log.asynchronous_metric_log.table }}
+ + {{ clickhouse_tables_log.asynchronous_metric_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.asynchronous_metric_log.ttl is defined %} + {{ clickhouse_tables_log.asynchronous_metric_log.ttl }} +{% endif %} +
+ + + + + + {{ clickhouse_tables_log.opentelemetry_span_log.engine }} + + {{ clickhouse_tables_log.opentelemetry_span_log.database }} + {{ clickhouse_tables_log.opentelemetry_span_log.table }}
+ {{ clickhouse_tables_log.opentelemetry_span_log.flush_interval_milliseconds }} +
+ + + + + {{ clickhouse_tables_log.crash_log.database }} + {{ clickhouse_tables_log.crash_log.table }}
+ {{ clickhouse_tables_log.crash_log.partition_by }} + {{ clickhouse_tables_log.crash_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.crash_log.ttl is defined %} + {{ clickhouse_tables_log.crash_log.ttl }} +{% endif %} +
+ + + + {{ clickhouse_tables_log.session_log.database }} + {{ clickhouse_tables_log.session_log.table }}
+ {{ clickhouse_tables_log.session_log.partition_by }} + {{ clickhouse_tables_log.session_log.flush_interval_milliseconds }} +{% if clickhouse_tables_log.session_log.ttl is defined %} + {{ clickhouse_tables_log.session_log.ttl }} +{% endif %} +
+ + + + + +{% if clickhouse_config.regions_hierarchy_file is defined %} + {{ clickhouse_config.regions_hierarchy_file }} +{% endif %} + + + +{% if clickhouse_config.regions_names_files_path is defined %} + {{ clickhouse_config.regions_names_files_path }} +{% endif %} + + +{% if clickhouse_config.top_level_domains_path is defined %} + {{ clickhouse_config.top_level_domains_path }} +{% endif %} + + + +{% if clickhouse_config.top_level_domains is defined %} +{% for domain in clickhouse_config.top_level_domains %} + <{{ domain.name }}>{{ domain.path }} +{% endfor %} +{% endif %} + + + + {{ clickhouse_config.dictionaries_config }} + + + {{ clickhouse_config.user_defined_executable_functions_config }} + + + +{% if clickhouse_compression is defined %} + +{% for case in clickhouse_compression %} + +{% if case.min_part_size is defined %} + {{ case.min_part_size }} +{% endif %} +{% if case.min_part_size_ratio is defined %} + {{ case.min_part_size_ratio }} +{% endif %} +{% if case.method is defined %} + {{ case.method }} +{% endif %} +{% if case.level is defined %} + {{ case.level }} +{% endif %} + +{% endfor %} + +{% endif %} + + + + + + + + + + + + + + + + + + + + + + + +{% if clickhouse_encryption is defined %} + <{{ clickhouse_encryption.encryption }}> +{% for key in clickhouse_encryption.elements %} + <{{ key.type }}{% if key.id is defined %} id={{ key.id }}{% endif %}{% if key.from_env is defined %} from_env={{ key.from_env }}{% endif %}>{% if key.value is defined %}{{ key.value }}{% endif %} +{% endfor %} +{% if clickhouse_encryption.current_key_id is defined %} + {{ clickhouse_encryption.current_key_id }} +{% endif %} +{% if clickhouse_encryption.nonce is defined %} + {{ clickhouse_encryption.nonce }} +{% endif %} + +{% endif %} + + + +{% if clickhouse_default_replica_path is defined %} + {{ clickhouse_default_replica_path }} +{% endif %} +{% if clickhouse_default_replica_name is defined %} + {{ clickhouse_default_replica_name }} +{% endif %} + + + +{% if clickhouse_zookeeper is defined and clickhouse_distributed_ddl is defined %} + + + {{ clickhouse_distributed_ddl.path }} + + + +{% if clickhouse_distributed_ddl.profile is defined %} + {{ clickhouse_distributed_ddl.profile }} +{% endif %} + + + +{% if clickhouse_distributed_ddl.pool_size is defined %} + {{ clickhouse_distributed_ddl.pool_size }} +{% endif %} + + + + + +{% if clickhouse_distributed_ddl.task_max_lifetime %} + {{ clickhouse_distributed_ddl.task_max_lifetime }} +{% endif %} + + + +{% if clickhouse_distributed_ddl.cleanup_delay_period %} + {{ clickhouse_distributed_ddl.cleanup_delay_period }} +{% endif %} + + + +{% if clickhouse_distributed_ddl.max_tasks_in_queue %} + {{ clickhouse_distributed_ddl.max_tasks_in_queue }} +{% endif %} + +{% endif %} + + + +{% if clickhouse_mergetree_settings is defined %} + +{% for setting in clickhouse_mergetree_settings|dict2items %} + <{{ setting.key }}>{{ setting.value }} +{% endfor %} + +{% endif %} + + + + +{% if clickhouse_config.max_table_size_to_drop is defined %} + {{ clickhouse_config.max_table_size_to_drop }} +{% endif %} +{% if clickhouse_config.max_partition_size_to_drop is defined %} + {{ clickhouse_config.max_partition_size_to_drop }} +{% endif %} + + +{% if clickhouse_graphitemergetree_settings is defined %} + <{{ clickhouse_graphitemergetree_settings.name }}> +{% if clickhouse_graphitemergetree_settings.pattern is defined %} + + {{ clickhouse_graphitemergetree_settings.pattern.regexp }} +{% if clickhouse_graphitemergetree_settings.pattern.function is defined %} + {{ clickhouse_graphitemergetree_settings.pattern.function }} +{% endif %} +{% for retention in clickhouse_graphitemergetree_settings.pattern.retention %} + + {{ retention.age }} + {{ retention.precision }} + +{% endfor %} + +{% endif %} + +{% if clickhouse_graphitemergetree_settings.default.function is defined %} + {{ clickhouse_graphitemergetree_settings.default.function }} +{% endif %} +{% for retention in clickhouse_graphitemergetree_settings.default.retention %} + + {{ retention.age }} + {{ retention.precision }} + +{% endfor %} + + +{% endif %} + + + {{ clickhouse_config.format_schema_path }} + + +{% if clickhouse_query_masking_rules is defined %} + +{% for rule in clickhouse_query_masking_rules %} + + {{ rule.name }} + {{ rule.regexp }} + {{ rule.replace }} + +{% endfor %} + +{% endif %} + + +{% if clickhouse_http_handlers is defined %} + +{% for rule in clickhouse_http_handlers %} + +{% if rule.url is defined %} + {{ rule.url }} +{% endif %} +{% if rule.methods is defined %} + {{ rule.methods }} +{% endif %} +{% if rule.headers is defined %} + {{ rule.headers }} +{% endif %} + +{% if rule.handler.type is defined %} + {{ rule.handler.type }} +{% endif %} +{% if rule.handler.type == 'predefined_query_handler' %} + {{ rule.handler.query }} +{% elif rule.handler.type == 'dynamic_query_handler' %} + {{ rule.handler.query_param_name }} +{% endif %} +{% if rule.handler.status is defined %} + {{ rule.handler.status }} +{% endif %} +{% if rule.handler.content_type is defined %} + {{ rule.handler.content_type }} +{% endif %} +{% if rule.handler.response_content is defined %} + {{ rule.handler.response_content }} +{% endif %} + + +{% endfor %} + +{% endif %} + + + + + + {{ clickhouse_send_crash_reports.enabled }} + + {{ clickhouse_send_crash_reports.anonymize }} + + + {{ clickhouse_send_crash_reports.endpoint }} + + + + +{% if clickhouse_config.disable_internal_dns_cache is defined %} + {{ clickhouse_config.disable_internal_dns_cache }} +{% endif %} + + + +{% if clickhouse_rocksdb is defined %} + +{% if clickhouse_rocksdb.db_options is defined %} + +{% for option in clickhouse_rocksdb.db_options|dict2items %} + <{{ option.key }}>{{ option.value }} +{% endfor %} + +{% endif %} +{% if clickhouse_rocksdb.db_options is defined %} + +{% for option in clickhouse_rocksdb.cf_options|dict2items %} + <{{ option.key }}>{{ option.value }} +{% endfor %} + +{% endif %} +{% if clickhouse_rocksdb.table_options is defined %} + +{% for table in clickhouse_rocksdb.table_options %} + + {{ table.name }} + +{% for option in table.options|dict2items %} + <{{ option.key }}>{{ option.value }} +{% endfor %} + + +{% for option in table.cf_options|dict2items %} + <{{ option.key }}>{{ option.value }} +{% endfor %} + +
+{% endfor %} +
+{% endif %} +
+{% endif %} + +{% if clickhouse_config_xml_extra_config is defined %} +{{ clickhouse_config_xml_extra_config|indent(2, true) }} +{% endif %} +
diff --git a/ansible/roles/clickhouse/templates/copier-config.xml.j2 b/ansible/roles/clickhouse/templates/copier-config.xml.j2 new file mode 100644 index 00000000..66c0965c --- /dev/null +++ b/ansible/roles/clickhouse/templates/copier-config.xml.j2 @@ -0,0 +1,20 @@ + + + + {{ clickhouse_copier_logger_level }} + {{ clickhouse_copier_logger_log }} + {{ clickhouse_copier_logger_errorlog }} + {{ clickhouse_copier_logger_size }} + {{ clickhouse_copier_logger_count }} + +{% if clickhouse_zookeeper is defined %} + +{% for node in clickhouse_zookeeper %} + + {{ node.host }} + {{ node.port }} + +{% endfor %} + +{% endif %} + \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 new file mode 100644 index 00000000..ac7d3fc2 --- /dev/null +++ b/ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 @@ -0,0 +1,26 @@ +{{ ansible_managed | comment }} +[Unit] +Description=ClickHouse JDBC Bridge +Documentation={{ clickhouse_jdbc_bridge_documentation_link | default('') }} +Requires=network-online.target +After=network-online.target + +[Service] +PrivateTmp={{ clickhouse_private_tmp }} +Type=simple +User={{ clickhouse_jdbc_bridge_user }} +Group={{ clickhouse_jdbc_bridge_group }} +RuntimeDirectory={{ clickhouse_jdbc_bridge_service }} +LimitNOFILE={{ clickhouse_limit_no_file }} + +ExecStart=/usr/local/bin/clickhouse-jdbc-bridge +ExecReload=/bin/kill -HUP $MAINPID +KillSignal=SIGTERM +Restart=always +RestartSec=30 + +LimitCORE=infinity +CapabilityBoundingSet=CAP_NET_ADMIN CAP_IPC_LOCK CAP_SYS_NICE + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 new file mode 100644 index 00000000..05c9d1e4 --- /dev/null +++ b/ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 @@ -0,0 +1,6 @@ +{# { +{%- for key, value in clickhouse_jdbc_bridge_datasources.items() recursive %} + "{{ key }}": {% if value is string %}"{{ value }}"{% elif value is mapping %}{{ "{" }}{{ loop(value.items()) | indent(4) }} + {{ "}" }}{% elif value is sequence %}{{ value | tojson }}{% else %}{{ value }}{% endif %}{{ "," if not loop.last else "" }}{% endfor %} +} #} +{{ item.datasource | to_json }} diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 new file mode 100644 index 00000000..3239cb81 --- /dev/null +++ b/ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 @@ -0,0 +1,14 @@ +handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler + +java.util.logging.ConsoleHandler.level={{ clickhouse_jdbc_bridge_log_level }} +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s %n + +java.util.logging.FileHandler.level={{ clickhouse_jdbc_bridge_log_level }} +java.util.logging.FileHandler.pattern={{ clickhouse_jdbc_bridge_log_file }} +java.util.logging.FileHandler.limit=10000000 +java.util.logging.FileHandler.count=5 +java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s %n + +.level={{ clickhouse_jdbc_bridge_log_level }} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 new file mode 100644 index 00000000..2be77319 --- /dev/null +++ b/ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 @@ -0,0 +1,5 @@ +#!/bin/bash + +{% if clickhouse_jdbc_bridge_env is defined %} +{{ clickhouse_jdbc_bridge_env|indent(0, true) }} +{% endif %} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/users.xml.j2 b/ansible/roles/clickhouse/templates/users.xml.j2 new file mode 100644 index 00000000..ccdb0ba6 --- /dev/null +++ b/ansible/roles/clickhouse/templates/users.xml.j2 @@ -0,0 +1,190 @@ + + + + + + + +{% if clickhouse_default_profiles is defined %} +{% for profile in clickhouse_default_profiles %} + <{{ profile }}> +{% for item in clickhouse_default_profiles[profile] %} + <{{ item }}>{{ clickhouse_default_profiles[profile][item] }} +{% endfor %} + +{% endfor %} +{% endif %} + +{% if clickhouse_custom_profiles is defined %} +{% for profile in clickhouse_custom_profiles %} + <{{ profile }}> +{% for item in clickhouse_custom_profiles[profile] %} + <{{ item }}>{{ clickhouse_custom_profiles[profile][item] }} +{% endfor %} + +{% endfor %} +{% endif %} + + + + + +{% if clickhouse_default_users is defined %} +{% for user in clickhouse_default_users %} + <{{ user.name }}> +{% if user.password is defined %} + {{ user.password }} +{% elif user.password_sha256_hex is defined %} + {{ user.password_sha256_hex }} +{% elif user.password_double_sha1_hex is defined %} + {{ user.password_double_sha1_hex }} +{% endif %} + +{% for network in user.networks %} + {{ network }} +{% endfor %} + + {{ user.profile }} +{% if user.databases is defined %} + +{% for database in user.databases %} + {{ database }} +{% endfor %} + +{% endif %} + {{ user.quota }} +{% if user.access_management is defined%} + {{ user.access_management }} +{% endif %} +{% if user.named_collection_control is defined%} + {{ user.named_collection_control }} +{% endif %} +{% if user.show_named_collections is defined%} + {{ user.show_named_collections }} +{% endif %} +{% if user.show_named_collections_secrets is defined%} + {{ user.show_named_collections_secrets }} +{% endif %} + +{% endfor %} +{% endif %} + +{% if clickhouse_admin_users is defined %} +{% for user in clickhouse_admin_users %} + <{{ user.name }}> +{% if user.password is defined %} + {{ user.password }} +{% elif user.password_sha256_hex is defined %} + {{ user.password_sha256_hex }} +{% elif user.password_double_sha1_hex is defined %} + {{ user.password_double_sha1_hex }} +{% endif %} + +{% for network in user.networks %} + {{ network }} +{% endfor %} + + {{ user.profile }} +{% if user.databases is defined %} + +{% for database in user.databases %} + {{ database }} +{% endfor %} + +{% endif %} + {{ user.quota }} +{% if user.access_management is defined%} + {{ user.access_management }} +{% endif %} +{% if user.named_collection_control is defined%} + {{ user.named_collection_control }} +{% endif %} +{% if user.show_named_collections is defined%} + {{ user.show_named_collections }} +{% endif %} +{% if user.show_named_collections_secrets is defined%} + {{ user.show_named_collections_secrets }} +{% endif %} + +{% endfor %} +{% endif %} + +{% if clickhouse_custom_users_xml is defined %} +{% for user in clickhouse_custom_users_xml %} + <{{ user.name }}> +{% if user.password is defined %} + {{ user.password }} +{% elif user.password_sha256_hex is defined %} + {{ user.password_sha256_hex }} +{% elif user.password_double_sha1_hex is defined %} + {{ user.password_double_sha1_hex }} +{% endif %} + +{% for network in user.networks %} + {{ network }} +{% endfor %} + + {{ user.profile }} + {{ user.quota }} +{% if user.databases is defined %} + +{% for database in user.databases %} + {{ database }} +{% endfor %} + +{% endif %} +{% if user.access_management is defined%} + {{ user.access_management }} +{% endif %} +{% if user.named_collection_control is defined%} + {{ user.named_collection_control }} +{% endif %} +{% if user.show_named_collections is defined%} + {{ user.show_named_collections }} +{% endif %} +{% if user.show_named_collections_secrets is defined%} + {{ user.show_named_collections_secrets }} +{% endif %} + +{% endfor %} +{% endif %} + + + + + +{% if clickhouse_default_quotas is defined %} +{% for quota in clickhouse_default_quotas %} + <{{ quota.name }}> + + {{ quota.duration }} + {{ quota.queries }} + {{ quota.errors }} + {{ quota.result_rows }} + {{ quota.read_rows }} + {{ quota.execution_time }} + + +{% endfor %} +{% endif %} +{% if clickhouse_custom_quotas_xml is defined %} + +{% for quota in clickhouse_custom_quotas_xml %} + <{{ quota.name }}> + + {{ quota.duration }} + {{ quota.queries }} + {{ quota.errors }} + {{ quota.result_rows }} + {{ quota.read_rows }} + {{ quota.execution_time }} + + +{% endfor %} +{% endif %} + + +{% if clickhouse_user_xml_extra_config is defined %} +{{ clickhouse_user_xml_extra_config|indent(2, true) }} +{% endif %} + diff --git a/ansible/roles/clickhouse/test-requirements.txt b/ansible/roles/clickhouse/test-requirements.txt new file mode 100644 index 00000000..52e2f145 --- /dev/null +++ b/ansible/roles/clickhouse/test-requirements.txt @@ -0,0 +1,6 @@ +ansible==9.4.0 +molecule==24.2.0 +docker==7.0.0 +molecule-plugins[docker]==23.5.3 +ansible-lint==24.2.1 +yamllint==1.35.1 From a79555650a7ce9010d22f69fccb51a680b0ec588 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 02/16] clickhouse role: trim the vendored role to what devops uses Switches oonidata_clickhouse from idealista.clickhouse_role 3.5.1, which ansible-galaxy installed from GitHub, to this copy of 3.5.2, and removes the parts of it that this repo doesn't use, so that what's left can be read and reviewed: the JDBC bridge, clickhouse-copier, SQL-driven databases, copying config/users files from the playbook instead of templating them, the clickhouse_install_db toggle, the Galaxy metadata, upstream's CHANGELOG and README, and the standalone repo's scaffolding (CI, molecule tests, lint configs, Pipfiles, logo). The ansible-galaxy requirement goes. The README keeps the attribution and lists the changes. config.xml and users.xml render byte for byte as with the unmodified 3.5.2 for data1-3 and notebook1. Against 3.5.1, what production runs today, config.xml is the same, and users.xml gives the admin user the named_collection_control, show_named_collections and show_named_collections_secrets that vars already set (3.5.1 ignored them), so the first deploy restarts each node. --- ansible/requirements/ansible-galaxy.yml | 5 +- ansible/roles/clickhouse/.ansible-lint | 5 - ansible/roles/clickhouse/.gitattributes | 3 - .../clickhouse/.github/CODE_OF_CONDUCT.md | 74 - .../roles/clickhouse/.github/CONTRIBUTING.md | 100 -- .../clickhouse/.github/ISSUE_TEMPLATE.md | 36 - .../.github/PULL_REQUEST_TEMPLATE.md | 26 - ansible/roles/clickhouse/.gitignore | 361 ----- ansible/roles/clickhouse/.travis.yml | 19 - ansible/roles/clickhouse/.yamllint | 32 - ansible/roles/clickhouse/CHANGELOG.md | 214 --- ansible/roles/clickhouse/Pipfile | 17 - ansible/roles/clickhouse/Pipfile.lock | 835 ----------- ansible/roles/clickhouse/README.md | 200 +-- ansible/roles/clickhouse/defaults/main.yml | 64 +- ansible/roles/clickhouse/handlers/main.yml | 12 - ansible/roles/clickhouse/logo.gif | Bin 24279 -> 0 bytes ansible/roles/clickhouse/meta/main.yml | 17 - .../clickhouse/molecule/default/Dockerfile.j2 | 18 - .../config/config.d/log_disable.xml | 13 - .../default/config_files/config/config.xml | 1257 ----------------- .../config_files/copier/copier-config.xml | 24 - .../users/users.d/log_queries.xml | 8 - .../default/config_files/users/users.xml | 124 -- .../clickhouse/molecule/default/converge.yml | 5 - .../files/jdbc-drivers/file.drivers.here | 0 .../default/group_vars/clickhouse_group.yml | 226 --- .../clickhouse/molecule/default/molecule.yml | 50 - .../molecule/default/tests/test_app.yml | 98 -- .../clickhouse/molecule/default/verify.yml | 60 - .../clickhouse/tasks/config/clickhouse.yml | 48 +- .../roles/clickhouse/tasks/config/copier.yml | 21 - .../clickhouse/tasks/config/databases.yml | 49 - ansible/roles/clickhouse/tasks/main.yml | 43 +- .../tasks/service/jdbc-bridge-Debian.yml | 103 -- .../clickhouse/templates/DDL/DATABASE.j2 | 5 - .../clickhouse/templates/copier-config.xml.j2 | 20 - .../clickhouse-jdbc-bridge.service.j2 | 26 - .../templates/jdbc-bridge/datasource.json.j2 | 6 - .../jdbc-bridge/logging.properties.j2 | 14 - .../templates/jdbc-bridge/set-env.sh.j2 | 5 - .../roles/clickhouse/test-requirements.txt | 6 - .../roles/oonidata_clickhouse/tasks/main.yml | 2 +- docs/Runbooks.md | 2 +- 44 files changed, 22 insertions(+), 4231 deletions(-) delete mode 100644 ansible/roles/clickhouse/.ansible-lint delete mode 100644 ansible/roles/clickhouse/.gitattributes delete mode 100644 ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md delete mode 100644 ansible/roles/clickhouse/.github/CONTRIBUTING.md delete mode 100644 ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md delete mode 100644 ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 ansible/roles/clickhouse/.gitignore delete mode 100644 ansible/roles/clickhouse/.travis.yml delete mode 100644 ansible/roles/clickhouse/.yamllint delete mode 100644 ansible/roles/clickhouse/CHANGELOG.md delete mode 100644 ansible/roles/clickhouse/Pipfile delete mode 100644 ansible/roles/clickhouse/Pipfile.lock delete mode 100644 ansible/roles/clickhouse/logo.gif delete mode 100644 ansible/roles/clickhouse/meta/main.yml delete mode 100644 ansible/roles/clickhouse/molecule/default/Dockerfile.j2 delete mode 100644 ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml delete mode 100644 ansible/roles/clickhouse/molecule/default/config_files/config/config.xml delete mode 100644 ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml delete mode 100644 ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml delete mode 100644 ansible/roles/clickhouse/molecule/default/config_files/users/users.xml delete mode 100644 ansible/roles/clickhouse/molecule/default/converge.yml delete mode 100644 ansible/roles/clickhouse/molecule/default/files/jdbc-drivers/file.drivers.here delete mode 100644 ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml delete mode 100644 ansible/roles/clickhouse/molecule/default/molecule.yml delete mode 100644 ansible/roles/clickhouse/molecule/default/tests/test_app.yml delete mode 100644 ansible/roles/clickhouse/molecule/default/verify.yml delete mode 100644 ansible/roles/clickhouse/tasks/config/copier.yml delete mode 100644 ansible/roles/clickhouse/tasks/config/databases.yml delete mode 100644 ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml delete mode 100644 ansible/roles/clickhouse/templates/DDL/DATABASE.j2 delete mode 100644 ansible/roles/clickhouse/templates/copier-config.xml.j2 delete mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 delete mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 delete mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 delete mode 100644 ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 delete mode 100644 ansible/roles/clickhouse/test-requirements.txt diff --git a/ansible/requirements/ansible-galaxy.yml b/ansible/requirements/ansible-galaxy.yml index 5172cbec..6019e101 100644 --- a/ansible/requirements/ansible-galaxy.yml +++ b/ansible/requirements/ansible-galaxy.yml @@ -3,10 +3,7 @@ roles: version: 0.26.0 - src: geerlingguy.certbot version: 5.2.0 - - src: https://github.com/idealista/clickhouse_role - scm: git - version: 3.5.1 - name: idealista.clickhouse_role + # The clickhouse role is vendored from idealista/clickhouse_role in ansible/roles/clickhouse - src: https://github.com/ooni/airflow-role.git scm: git name: ooni.airflow_role diff --git a/ansible/roles/clickhouse/.ansible-lint b/ansible/roles/clickhouse/.ansible-lint deleted file mode 100644 index f8b56157..00000000 --- a/ansible/roles/clickhouse/.ansible-lint +++ /dev/null @@ -1,5 +0,0 @@ -exclude_paths: - - ./molecule -parseable: true -use_default_rules: true -verbosity: 1 diff --git a/ansible/roles/clickhouse/.gitattributes b/ansible/roles/clickhouse/.gitattributes deleted file mode 100644 index d08e2bfc..00000000 --- a/ansible/roles/clickhouse/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -*.yml linguist-detectable=true -*.yaml linguist-detectable=true -*.html linguist-detectable=false diff --git a/ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md b/ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 4e06e5dc..00000000 --- a/ansible/roles/clickhouse/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,74 +0,0 @@ -# Contributor Covenant Code of Conduct - -## 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. - -## 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 unacceptable behavior by participants 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 - - -## Our Responsibilities - -Project 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 maintainers 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. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [labs@idealista.com](mailto:labs@idealista.com). All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org \ No newline at end of file diff --git a/ansible/roles/clickhouse/.github/CONTRIBUTING.md b/ansible/roles/clickhouse/.github/CONTRIBUTING.md deleted file mode 100644 index bc1f6f3c..00000000 --- a/ansible/roles/clickhouse/.github/CONTRIBUTING.md +++ /dev/null @@ -1,100 +0,0 @@ -# Contributing to Idealista - -:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: - -The following is a set of guidelines for contributing to Idealista's repositories, which are hosted in the [Idealista Organization](https://github.com/idealista) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. - -#### Table Of Contents - -[Code of Conduct](#code-of-conduct) - -[How Can I Contribute?](#how-can-i-contribute) - * [Reporting Bugs](#reporting-bugs) - * [Suggesting Enhancements](#suggesting-enhancements) - * [Pull Requests](#pull-requests) - * [Changelog](#changelog) - -[Styleguides](#styleguides) - * [Git Commit Messages](#git-commit-messages) - -## Code of Conduct - -This project and everyone participating in it is governed by the [Idealista Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [labs@idealista.com](mailto:labs@idealista.com). - - -## How Can I Contribute? - -### Reporting Bugs - -This section guides you through submitting a bug report for Idealista. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. - -Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](ISSUE_TEMPLATE.md), the information it asks for helps us resolve issues faster. - -> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. - -#### Before Submitting A Bug Report - -* **Check the last version.** Check if you can reproduce the problem in the latest version of the project. -* **Check the FAQ of the project** for a list of common questions and problems. -* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aidealista)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. - -#### How Do I Submit A (Good) Bug Report? - -Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on the project repository and provide the following information by filling in [the template](ISSUE_TEMPLATE.md). - -Explain the problem and include additional details to help maintainers reproduce the problem: - -* **Use a clear and descriptive title** for the issue to identify the problem. -* **Describe the exact steps which reproduce the problem** in as many details as possible. -* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. -* **Explain which behavior you expected to see instead and why.** - -### Suggesting Enhancements - -This section guides you through submitting an enhancement suggestion for Idealista, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. - -Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](ISSUE_TEMPLATE.md), including the steps that you imagine you would take if the feature you're requesting existed. - -#### Before Submitting An Enhancement Suggestion - -* **Check the last version.** Check if you can reproduce the problem in the latest version of the project. -* **Check the FAQ of the project** for a list of common questions and problems. -* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aidealista)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. - -#### How Do I Submit A (Good) Enhancement Suggestion? - -Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on the project repository and provide the following information by filling in [the template](ISSUE_TEMPLATE.md): - -* **Use a clear and descriptive title** for the issue to identify the suggestion. -* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. -* **Provide specific examples to demonstrate the steps**. -* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. -* **Explain why this enhancement would be useful**. -* **List some other text editors or applications where this enhancement exists.** -* **Specify which version are you're using.** - -### Pull Requests - -* Fill in [the required template](PULL_REQUEST_TEMPLATE.md) -* Any pull request should have **idealista:develop** as base branch. - -### Changelog - -Every project has a CHANGELOG.md file. Once your code is ready to be merged please fill the issue after the **Unreleased** section as explained: - -* For an enhancement, fill the issue after the **Added** subsection (create it if doesn't exists) -* For a fixed bug, fill the issue after the **Fixed** subsection (create it if doesn't exists) -* For an improvement, fill the issue after the **Changed** subsection (create it if doesn't exists) - -Then write the issue info this way: - -- *[#29](https://github.com/idealista/nginx-role/issues/29) Support debian stretch* @jmonterrubio - -## Styleguides - -### Git Commit Messages - -* Use the present tense ("Add feature" not "Added feature") -* Use the imperative mood ("Move cursor to..." not "Moves cursor to...") -* Limit the first line to 72 characters or less -* Reference issues and pull requests liberally after the first line \ No newline at end of file diff --git a/ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md b/ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index ce3b4bed..00000000 --- a/ansible/roles/clickhouse/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,36 +0,0 @@ - - -### Prerequisites - -* [ ] Put an X between the brackets on this line if you have done all of the following: - * Checked that your issue isn't already filled: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aidealista - * Checked that there is not already provided the described functionality - -### Description - -[Description of the issue] - -### Steps to Reproduce - -1. [First Step] -2. [Second Step] -3. [and so on...] - -**Expected behavior:** [What you expect to happen] - -**Actual behavior:** [What actually happens] - -**Reproduces how often:** [What percentage of the time does it reproduce?] - -### Versions - -The version/s you notice the behavior. - -### Additional Information - -Any additional information, configuration or data that might be necessary to reproduce the issue. \ No newline at end of file diff --git a/ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md b/ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 2eb9c295..00000000 --- a/ansible/roles/clickhouse/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,26 +0,0 @@ -### Requirements - -* Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. -* All new code requires tests to ensure against regressions -* Remember to set **idealista:develop** as base branch; - -### Description of the Change - - - - -### Benefits - - - -### Possible Drawbacks - - - -### Applicable Issues - - \ No newline at end of file diff --git a/ansible/roles/clickhouse/.gitignore b/ansible/roles/clickhouse/.gitignore deleted file mode 100644 index 77bb3b65..00000000 --- a/ansible/roles/clickhouse/.gitignore +++ /dev/null @@ -1,361 +0,0 @@ -# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig - -# Created by https://www.toptal.com/developers/gitignore/api/ansible,git,intellij+all,pycharm+all,python,virtualenv,visualstudiocode -# Edit at https://www.toptal.com/developers/gitignore?templates=ansible,git,intellij+all,pycharm+all,python,virtualenv,visualstudiocode - -### Ansible ### -*.retry - -### Git ### -# Created by git for backups. To disable backups in Git: -# $ git config --global mergetool.keepBackup false -*.orig - -# Created by git when using merge tools for conflicts -*.BACKUP.* -*.BASE.* -*.LOCAL.* -*.REMOTE.* -*_BACKUP_*.txt -*_BASE_*.txt -*_LOCAL_*.txt -*_REMOTE_*.txt - -### Intellij+all ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Intellij+all Patch ### -# Ignores the whole .idea folder and all .iml files -# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 - -.idea/ - -# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 - -*.iml -modules.xml -.idea/misc.xml -*.ipr - -# Sonarlint plugin -.idea/sonarlint - -### PyCharm+all ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff - -# AWS User-specific - -# Generated files - -# Sensitive or high-churn files - -# Gradle - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake - -# Mongo Explorer plugin - -# File-based project format - -# IntelliJ - -# mpeltonen/sbt-idea plugin - -# JIRA plugin - -# Cursive Clojure plugin - -# Crashlytics plugin (for Android Studio and IntelliJ) - -# Editor-based Rest Client - -# Android studio 3.1+ serialized cache file - -### PyCharm+all Patch ### -# Ignores the whole .idea folder and all .iml files -# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 - - -# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 - - -# Sonarlint plugin - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -### VirtualEnv ### -# Virtualenv -# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ -[Bb]in -[Ii]nclude -[Ll]ib -[Ll]ib64 -[Ll]ocal -[Ss]cripts -pyvenv.cfg -pip-selfcheck.json - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - -# Support for Project snippet scope -!.vscode/*.code-snippets - -# End of https://www.toptal.com/developers/gitignore/api/ansible,git,intellij+all,pycharm+all,python,virtualenv,visualstudiocode - -# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) - -tests/playbook.retry -tests/.cache -__pycache__ -.pytest_cache -.molecule -.cache - -*.iml -.idea -.project - -*.pyc -**/.vscode -/logs \ No newline at end of file diff --git a/ansible/roles/clickhouse/.travis.yml b/ansible/roles/clickhouse/.travis.yml deleted file mode 100644 index a5a86edd..00000000 --- a/ansible/roles/clickhouse/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- - -dist: focal -language: python -python: "3.11" -os: linux -services: - - docker -install: - - pip install -I pipenv - - pipenv sync -env: - jobs: - - MOLECULE_DISTRO=debian:bullseye-slim -script: - - pipenv run molecule test - -notifications: - webhooks: https://galaxy.ansible.com/api/v1/notifications/ diff --git a/ansible/roles/clickhouse/.yamllint b/ansible/roles/clickhouse/.yamllint deleted file mode 100644 index fb79e4bf..00000000 --- a/ansible/roles/clickhouse/.yamllint +++ /dev/null @@ -1,32 +0,0 @@ ---- -# Based on ansible-lint config -extends: default - -ignore: | - molecule/**/tests/ - .ansible-lint - -rules: - braces: - max-spaces-inside: 1 - level: error - brackets: - max-spaces-inside: 1 - level: error - colons: - max-spaces-after: -1 - level: error - commas: - max-spaces-after: -1 - level: error - comments: disable - empty-lines: - max: 3 - level: error - hyphens: - level: error - key-duplicates: enable - line-length: disable - new-lines: - type: unix - truthy: disable diff --git a/ansible/roles/clickhouse/CHANGELOG.md b/ansible/roles/clickhouse/CHANGELOG.md deleted file mode 100644 index 2c037245..00000000 --- a/ansible/roles/clickhouse/CHANGELOG.md +++ /dev/null @@ -1,214 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a changelog](https://github.com/olivierlacan/keep-a-changelog). - -## [Unreleased](https://github.com/idealista/clickhouse_role/tree/develop) - -## [3.5.1](https://github.com/idealista/clickhouse_role/tree/3.5.1) (2024-04-18) - -### :hammer_and_wrench: Fixed - -- [#73](https://github.com/idealista/clickhouse_role/issues/73) text_log table config crashes Clickhouse - -### :repeat: Updated - -- Tasks now are using FQDN -- Test requirements has been updated - -## [3.5.0](https://github.com/idealista/clickhouse_role/tree/3.5.0) (2024-01-31) - -### :heavy_plus_sign: Added - -- [#70](https://github.com/idealista/clickhouse_role/issues/70) Include extra config and users files -- Update default lts version to latest (23.8.9.54) - -## [3.4.1](https://github.com/idealista/clickhouse_role/tree/3.4.1) (2024-01-26) - -### :hammer_and_wrench: Fixed - -- [#67](https://github.com/idealista/clickhouse_role/issues/67) Fix log tables partitioning - -## [3.4.0](https://github.com/idealista/clickhouse_role/tree/3.4.0) (2023-11-17) - -### :heavy_plus_sign: Added - -- [#64](https://github.com/idealista/clickhouse_role/issues/64) Add clickhouse_keeper and default replica params enhancement - -### :repeat: Updated - -- [#65](https://github.com/idealista/clickhouse_role/issues/65) Update to use ansible_os_family var instead of ansible_distribution var for main.yml - -## [3.3.6](https://github.com/idealista/clickhouse_role/tree/3.3.6) (2023-06-09) - -### :hammer_and_wrench: Fixed - -- [#59](https://github.com/idealista/clickhouse_role/issues/59) Fix role wiping when there are users from readonly storage - -## [3.3.5](https://github.com/idealista/clickhouse_role/tree/3.3.5) (2023-06-09) - -### :heavy_plus_sign: Added - -- [#56](https://github.com/idealista/clickhouse_role/issues/56) Add support for several ldap servers and role mappings - -## [3.3.4](https://github.com/idealista/clickhouse_role/tree/3.3.4) (2023-05-29) - -### :heavy_plus_sign: Added - -- [#53](https://github.com/idealista/clickhouse_role/issues/53) Add LDAP support for authentication. - -## [3.3.3](https://github.com/idealista/clickhouse_role/tree/3.3.3) (2023-03-27) - -### :repeat: Updated - -- [#50](https://github.com/idealista/clickhouse_role/issues/50) [REFACTOR] Adecuate service file changes to default CH version - -### :hammer_and_wrench: Fixed - -- Some typos and quotation - -## [3.3.2](https://github.com/idealista/clickhouse_role/tree/3.3.2) (2023-01-18) - -### :heavy_plus_sign: Added - -- [#44](https://github.com/idealista/clickhouse_role/issues/44) Allow extra packages installation. - -### :hammer_and_wrench: Fixed - -- [#45](https://github.com/idealista/clickhouse_role/issues/45) Ensure existence of base path where clickhouse will be installed - -## [3.3.1](https://github.com/idealista/clickhouse_role/tree/3.3.1) (2023-01-18) - -### :heavy_plus_sign: Added - -- [#41](https://github.com/idealista/clickhouse_role/issues/41) add TTL to system tables and default TTL values. - -## [3.3.0](https://github.com/idealista/clickhouse_role/tree/3.3.0) (2022-11-12) - -### :hammer_and_wrench: Fixed - -- [#36](https://github.com/idealista/clickhouse_role/issues/36) Ansible galaxy meta malformed - -### :heavy_plus_sign: Added - -- [#35](https://github.com/idealista/clickhouse_role/issues/35) Clickhouse copier basic configuration - -### :repeat: Updated - -- [#34](https://github.com/idealista/clickhouse_role/issues/34) The Clickhouse JDBC engine requirements installation - -## [3.2.1](https://github.com/idealista/clickhouse_role/tree/3.2.1) (2022-08-30) - -### :hammer_and_wrench: Fixed - -- [#30](https://github.com/idealista/clickhouse_role/issues/30) Fixed clickhouse server fails after upgrading - -## [3.2.0](https://github.com/idealista/clickhouse_role/tree/3.2.0) (2022-06-01) - -### :heavy_plus_sign: Added - -- [#26](https://github.com/idealista/clickhouse_role/issues/26) New feature to manage GRANT perms and privs -- Corresponding vars to use that feature -- Testing vars for this feature in molecule/group_vars -- Necesary templates - -### :hammer_and_wrench: Fixed - -- Tasks with no_log set to False - -### :repeat: Updated - -- Readme -- Test requirements Ansible version to 5.2.0 -- Task tags -- Clickhouse default version - -## [3.1.1](https://github.com/idealista/clickhouse_role/tree/3.1.1) (2022-04-29) - -### :hammer_and_wrench: Fixed - -- [#23](https://github.com/idealista/clickhouse_role/issues/23) Fixed task trying to remove '_temporary_and_external_tables' database @santi-eidu - -## [3.1.0](https://github.com/idealista/clickhouse_role/tree/3.1.0) (2022-02-14) - -### :heavy_plus_sign: Added - -- Option to use custom config xml files -- [#18](https://github.com/idealista/clickhouse_role/issues/18) Option to create and remove basic users, roles, profiles, quotas, etc @ultraheroe - -### :repeat: Updated - -- Default ClickHouse version. -- Requirements versions. -- Readme instructions. -- Molecule verify update to use checksum instead of the deprecated command. -- Default molecule scenario and tests -- Tasks orders and role files skeleton -- [#20](https://github.com/idealista/clickhouse_role/issues/20) clickhouse_macros var refactor @ultraheroe - -### :hammer_and_wrench: Fixed - -- [#16](https://github.com/idealista/clickhouse_role/issues/16) Fix missing user, password and compression values in remote_servers @ultraheroe -- [#14](https://github.com/idealista/clickhouse_role/issues/14) Wait for service listening @jmonterrubio - -## [3.0.1](https://github.com/idealista/clickhouse_role/tree/3.0.1) (2021-12-14) - -### :hammer_and_wrench: Fixed - -- [#8](https://github.com/idealista/clickhouse_role/issues/8) Remove ansible deprecation warning @Pablohn26 -- [#10](https://github.com/idealista/clickhouse_role/issues/10) Fix paths permissions @ultraheroe -- [#11](https://github.com/idealista/clickhouse_role/issues/10) Allow custom tag values for replicated macro var @ultraheroe - -### :heavy_plus_sign: Added - -- Add .gitattributes. -- Add option to set CH release branch version. - -### :repeat: Changed - -- Update CH default version -- Update .travis python version to 3.9 -- Update test-requirements, ansible, molecule, etc -- Update Readme - -## [3.0.0](https://github.com/idealista/clickhouse_role/tree/3.0.0) (2021-11-23) - -### :hammer_and_wrench: Fixed - -- [#5](https://github.com/idealista/clickhouse_role/issues/5) Defined hkp apt keyserver protocol - -### :heavy_plus_sign: Added - -- [#4](https://github.com/idealista/clickhouse_role/issues/4) Required vars for new config templates - -### :repeat: Changed - -- [#4](https://github.com/idealista/clickhouse_role/issues/4) Fully parametrized config files -- .gitignore rules for various IDEs -- Deleted old stretch distro and add bullseye to .travis.yml -- Update test-requirements versions -- Update default version to latest (at this moment v21.11.2.2) -- Check how to check installed version -- Update molecule config and vars - -## [2.0.0](https://github.com/idealista/clickhouse_role/tree/2.0.0) (2020-11-18) - -- *[#1](https://github.com/idealista/clickhouse_role/issues/1) Fix mismatched var names* @frantsao -- *Fix systemd service config: prevent restarting after unit file changes; fix reloading (needs /bin/kill)* @frantsao -- *Fix default database creation* @frantsao -- *Fix missing listening host templating in config.xml* @frantsao -- *Fix admin user templating* @frantsao - -### Added - -- *Added var in order to control service behaviour in config changes* @frantsao - -### Changed - -- *Renamed vars in order to differentiate from other roles vars (BREAKING)* @frantsao - -## [1.0.0](https://github.com/idealista/clickhouse_role/tree/1.0.0) (2020-11-03) - -### Added - -- *First version* @xtianae7 diff --git a/ansible/roles/clickhouse/Pipfile b/ansible/roles/clickhouse/Pipfile deleted file mode 100644 index d0ceeb23..00000000 --- a/ansible/roles/clickhouse/Pipfile +++ /dev/null @@ -1,17 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -ansible = "==9.4.0" -molecule = "==24.2.0" -docker = "==7.0.0" -ansible-lint = "==24.2.1" -yamllint = "==1.35.1" -molecule-plugins = {extras = ["docker"], version = "==23.5.3"} - -[dev-packages] - -[requires] -python_version = "3.11" diff --git a/ansible/roles/clickhouse/Pipfile.lock b/ansible/roles/clickhouse/Pipfile.lock deleted file mode 100644 index 0b12d764..00000000 --- a/ansible/roles/clickhouse/Pipfile.lock +++ /dev/null @@ -1,835 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "c6770b0238200be2f18b9f45c0bbd8033c1c45c507bba63a07a277025f63efcd" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.11" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "ansible": { - "hashes": [ - "sha256:dd431c63380e18c3faca3288ebde8ce2f4f992363ab558a3c11c8f2032d90867", - "sha256:f1d67a2c21dbed3fee4fe579f750e5d20b5a5f13f4399f256a8a70f0505e62f7" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==9.4.0" - }, - "ansible-compat": { - "hashes": [ - "sha256:74a91807808a39af48ab6595811b9340d1458db26b138362f48bf39292190705", - "sha256:b3e9f9d7c3a1ce6222de444e9dc6fece7eba70ac64f2a0befdc4e2d542018b4a" - ], - "markers": "python_version >= '3.9'", - "version": "==4.1.11" - }, - "ansible-core": { - "hashes": [ - "sha256:111e55d358c2297ec0ce03ba98e6c5ce95947fdf50d878215eb8c183d0c275e4", - "sha256:f9dea5044a86fd95cc27099f4f5c3ae9beb23acf7c3b6331455726c47825922b" - ], - "markers": "python_version >= '3.10'", - "version": "==2.16.6" - }, - "ansible-lint": { - "hashes": [ - "sha256:c3619deaadf852c353decff7242589bd1339e29271552e4e37bfa8f67db4c56b", - "sha256:d9e11aa06429610f0b461ec5eb9adf7317d4b2abb78475a9a1ec7e8f8e2c1316" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==24.2.1" - }, - "attrs": { - "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" - ], - "markers": "python_version >= '3.7'", - "version": "==23.2.0" - }, - "black": { - "hashes": [ - "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d", - "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd", - "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33", - "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965", - "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070", - "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397", - "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745", - "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1", - "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665", - "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436", - "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb", - "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e", - "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6", - "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702", - "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8", - "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8", - "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3", - "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad", - "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf", - "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e", - "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641", - "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2" - ], - "markers": "python_version >= '3.8'", - "version": "==24.4.0" - }, - "bracex": { - "hashes": [ - "sha256:a27eaf1df42cf561fed58b7a8f3fdf129d1ea16a81e1fadd1d17989bc6384beb", - "sha256:efdc71eff95eaff5e0f8cfebe7d01adf2c8637c8c92edaf63ef348c241a82418" - ], - "markers": "python_version >= '3.8'", - "version": "==2.4" - }, - "certifi": { - "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" - ], - "markers": "python_version >= '3.6'", - "version": "==2024.2.2" - }, - "cffi": { - "hashes": [ - "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", - "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", - "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", - "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", - "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", - "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", - "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", - "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", - "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", - "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", - "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", - "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", - "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", - "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", - "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", - "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", - "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", - "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", - "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", - "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", - "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", - "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", - "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", - "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", - "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", - "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", - "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", - "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", - "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", - "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", - "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", - "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", - "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", - "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", - "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", - "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", - "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", - "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", - "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", - "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", - "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", - "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", - "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", - "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", - "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", - "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", - "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", - "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", - "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", - "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", - "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", - "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" - ], - "markers": "platform_python_implementation != 'PyPy'", - "version": "==1.16.0" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" - }, - "click": { - "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" - ], - "markers": "python_version >= '3.7'", - "version": "==8.1.7" - }, - "click-help-colors": { - "hashes": [ - "sha256:b33c5803eeaeb084393b1ab5899dc5476c7196b87a18713045afe76f840b42db", - "sha256:f4cabe52cf550299b8888f4f2ee4c5f359ac27e33bcfe4d61db47785a5cc936c" - ], - "version": "==0.9.4" - }, - "cryptography": { - "hashes": [ - "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee", - "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576", - "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d", - "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30", - "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413", - "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb", - "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da", - "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4", - "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd", - "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc", - "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8", - "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1", - "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc", - "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e", - "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8", - "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940", - "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400", - "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7", - "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16", - "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278", - "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74", - "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec", - "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1", - "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2", - "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c", - "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922", - "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a", - "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6", - "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1", - "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e", - "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac", - "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7" - ], - "markers": "python_version >= '3.7'", - "version": "==42.0.5" - }, - "distro": { - "hashes": [ - "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", - "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2" - ], - "markers": "python_version >= '3.6'", - "version": "==1.9.0" - }, - "docker": { - "hashes": [ - "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b", - "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==7.0.0" - }, - "enrich": { - "hashes": [ - "sha256:0a2ab0d2931dff8947012602d1234d2a3ee002d9a355b5d70be6bf5466008893", - "sha256:f29b2c8c124b4dbd7c975ab5c3568f6c7a47938ea3b7d2106c8a3bd346545e4f" - ], - "markers": "python_version >= '3.6'", - "version": "==1.2.7" - }, - "filelock": { - "hashes": [ - "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f", - "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4" - ], - "markers": "python_version >= '3.8'", - "version": "==3.13.4" - }, - "idna": { - "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" - ], - "markers": "python_version >= '3.5'", - "version": "==3.7" - }, - "jinja2": { - "hashes": [ - "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", - "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.3" - }, - "jsonschema": { - "hashes": [ - "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f", - "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5" - ], - "markers": "python_version >= '3.8'", - "version": "==4.21.1" - }, - "jsonschema-specifications": { - "hashes": [ - "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", - "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" - ], - "markers": "python_version >= '3.8'", - "version": "==2023.12.1" - }, - "markdown-it-py": { - "hashes": [ - "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", - "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" - ], - "markers": "python_version >= '3.8'", - "version": "==3.0.0" - }, - "markupsafe": { - "hashes": [ - "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", - "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", - "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", - "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", - "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", - "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", - "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", - "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", - "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", - "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", - "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", - "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", - "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", - "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", - "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", - "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", - "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", - "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", - "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", - "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", - "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", - "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", - "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", - "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", - "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", - "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", - "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", - "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", - "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", - "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", - "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", - "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", - "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", - "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", - "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", - "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", - "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", - "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", - "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", - "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", - "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", - "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", - "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", - "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", - "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", - "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", - "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", - "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", - "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", - "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", - "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", - "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", - "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", - "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", - "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", - "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", - "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", - "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", - "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", - "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.5" - }, - "mdurl": { - "hashes": [ - "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", - "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" - ], - "markers": "python_version >= '3.7'", - "version": "==0.1.2" - }, - "molecule": { - "hashes": [ - "sha256:47c982a7d05db7846da77fe7159deb946f26cafb2e388fc71812be0089a41776", - "sha256:eac7d9bf12d2bf5254e13a46a40357615c32e7003409f305c3940e93b65d7bea" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==24.2.0" - }, - "molecule-plugins": { - "extras": [ - "docker" - ], - "hashes": [ - "sha256:87f8ac8d5e9fe1cbdfb784d92b1fd08e7cb11bf02a9391bb34dcb93fadf7a3fc", - "sha256:a2b1437d532d736e3fbc6db7a69ec533e2334b2115ff9245a0b2772ed9738d23" - ], - "markers": "python_version >= '3.9'", - "version": "==23.5.3" - }, - "mypy-extensions": { - "hashes": [ - "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", - "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.0" - }, - "packaging": { - "hashes": [ - "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", - "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" - ], - "markers": "python_version >= '3.7'", - "version": "==24.0" - }, - "pathspec": { - "hashes": [ - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, - "platformdirs": { - "hashes": [ - "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", - "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" - ], - "markers": "python_version >= '3.8'", - "version": "==4.2.0" - }, - "pluggy": { - "hashes": [ - "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", - "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" - ], - "markers": "python_version >= '3.8'", - "version": "==1.4.0" - }, - "pycparser": { - "hashes": [ - "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", - "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" - ], - "markers": "python_version >= '3.8'", - "version": "==2.22" - }, - "pygments": { - "hashes": [ - "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", - "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" - ], - "markers": "python_version >= '3.7'", - "version": "==2.17.2" - }, - "pyyaml": { - "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" - ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" - }, - "referencing": { - "hashes": [ - "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844", - "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4" - ], - "markers": "python_version >= '3.8'", - "version": "==0.34.0" - }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, - "resolvelib": { - "hashes": [ - "sha256:04ce76cbd63fded2078ce224785da6ecd42b9564b1390793f64ddecbe997b309", - "sha256:d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf" - ], - "version": "==1.0.1" - }, - "rich": { - "hashes": [ - "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", - "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.1" - }, - "rpds-py": { - "hashes": [ - "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f", - "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c", - "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76", - "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e", - "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157", - "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f", - "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5", - "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05", - "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24", - "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1", - "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8", - "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b", - "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb", - "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07", - "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1", - "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6", - "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e", - "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e", - "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1", - "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab", - "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4", - "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17", - "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594", - "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d", - "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d", - "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3", - "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c", - "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66", - "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f", - "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80", - "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33", - "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f", - "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c", - "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022", - "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e", - "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f", - "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da", - "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1", - "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688", - "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795", - "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c", - "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98", - "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1", - "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20", - "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307", - "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4", - "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18", - "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294", - "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66", - "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467", - "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948", - "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e", - "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1", - "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0", - "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7", - "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd", - "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641", - "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d", - "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9", - "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1", - "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da", - "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3", - "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa", - "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7", - "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40", - "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496", - "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124", - "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836", - "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434", - "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984", - "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f", - "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6", - "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e", - "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461", - "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c", - "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432", - "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73", - "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58", - "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88", - "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337", - "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7", - "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863", - "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475", - "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3", - "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51", - "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf", - "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024", - "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40", - "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9", - "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec", - "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb", - "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7", - "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861", - "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880", - "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f", - "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd", - "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca", - "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58", - "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e" - ], - "markers": "python_version >= '3.8'", - "version": "==0.18.0" - }, - "ruamel.yaml": { - "hashes": [ - "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", - "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b" - ], - "markers": "python_version >= '3.7'", - "version": "==0.18.6" - }, - "ruamel.yaml.clib": { - "hashes": [ - "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", - "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", - "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", - "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", - "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", - "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b", - "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", - "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", - "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", - "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", - "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", - "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1", - "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", - "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675", - "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", - "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7", - "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", - "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312", - "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa", - "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", - "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b", - "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6", - "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3", - "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", - "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", - "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3", - "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe", - "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", - "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed", - "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", - "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", - "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", - "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", - "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", - "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", - "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", - "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", - "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", - "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", - "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942", - "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", - "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", - "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", - "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", - "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28", - "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d", - "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", - "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", - "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", - "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" - ], - "markers": "python_version < '3.13' and platform_python_implementation == 'CPython'", - "version": "==0.2.8" - }, - "selinux": { - "hashes": [ - "sha256:2a88b337ac46ad0f06f557b2806c3df62421972f766673dd8bf26732fb75a9ea", - "sha256:ecf7add45c939e9dda682c390a2cd0a845c94a4793a2cce9e8870d4ee9501f99" - ], - "markers": "sys_platform == 'linux'", - "version": "==0.3.0" - }, - "subprocess-tee": { - "hashes": [ - "sha256:b3c124993f8b88d1eb1c2fde0bc2069787eac720ba88771cba17e8c93324825d", - "sha256:eca56973a1c1237093c2055b2731bcaab784683b83f22c76f26e4c5763402e28" - ], - "markers": "python_version >= '3.8'", - "version": "==0.4.1" - }, - "urllib3": { - "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" - ], - "markers": "python_version >= '3.8'", - "version": "==2.2.1" - }, - "wcmatch": { - "hashes": [ - "sha256:24c19cedc92bc9c9e27f39db4e1824d72f95bd2cea32b254a47a45b1a1b227ed", - "sha256:c0088c7f6426cf6bf27e530e2b7b734031905f7e490475fd83c7c5008ab581b3" - ], - "markers": "python_version >= '3.8'", - "version": "==8.5.1" - }, - "yamllint": { - "hashes": [ - "sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3", - "sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.35.1" - } - }, - "develop": {} -} diff --git a/ansible/roles/clickhouse/README.md b/ansible/roles/clickhouse/README.md index e6341bc8..8735555d 100644 --- a/ansible/roles/clickhouse/README.md +++ b/ansible/roles/clickhouse/README.md @@ -1,192 +1,16 @@ -# Clickhouse Ansible role +# clickhouse -![GitHub release (latest by date)](https://img.shields.io/github/v/release/idealista/clickhouse_role?color=%23B62682) -[![Build Status](https://travis-ci.org/idealista/clickhouse_role.png)](https://travis-ci.org/idealista/clickhouse_role) [![Ansible Galaxy](https://img.shields.io/badge/galaxy-idealista.clickhouse_role-B62682.svg)](https://galaxy.ansible.com/idealista/clickhouse_role) +Installs and configures ClickHouse on Debian. -![Logo](https://raw.githubusercontent.com/idealista/clickhouse_role/main/logo.gif) +Vendored from [idealista/clickhouse_role](https://github.com/idealista/clickhouse_role) +by Idealista S.A.U. and contributors, tag `3.5.2` +(`5ca82f11c0bd554c4b52045a57cb0e0bc6addb61`, the last upstream release, December 2024), +under the Apache License 2.0, kept unchanged in `LICENSE`. OONI has modified the files +in this directory: the changes are listed below and in this repo's git history. Edit it +here like any other role in this repo. -This ansible role installs [Clickhouse](https://clickhouse.com/) in a Debian environment. It has been tested for Debian bullseye. +## Changes from upstream -This role has been generated using the [cookiecutter](https://github.com/cookiecutter/cookiecutter) tool, you can generate a similar role that fits your needs using the this [cookiecutter template](https://github.com/idealista/cookiecutter-ansible-role). - -- [Getting Started](#getting-started-checkered_flag) - - [Prerequisites](#prerequisites-ballot_box_with_check) - - [Installing](#Installing-inbox_tray ) -- [Usage](#usage-runner) -- [Testing](#testing-test_tube) -- [Built With](#built-with-building_construction) -- [Versioning](#versioning-card_file_box) -- [Authors](#authors-superhero) -- [License](#license-spiral_notepad) -- [Contributing](#contributing-construction_worker) - -## Getting Started :checkered_flag: - -These instructions will get you a copy of the role for your Ansible playbook. Once launched, it will install [Clickhouse](https://clickhouse.com/) in a Debian system. - -### Prerequisites :ballot_box_with_check: - -Ansible 5.x.x version installed. - -Molecule 3.x.x version installed. - -For testing purposes, [Molecule](https://molecule.readthedocs.io/) with [Docker](https://www.docker.com/) as driver and [Goss](https://github.com/aelsabbahy/goss) as verifier. - -### Installing :inbox_tray: - -Create or add to your roles dependency file (e.g requirements.yml): - -```yml -- src: idealista.clickhouse_role - scm: git - version: 3.2.0 - name: clickhouse_role -``` - -Install the role with ansible-galaxy command: - -```sh -ansible-galaxy install -p roles -r requirements.yml -f -``` - -Use in a playbook: - -```yml ---- -- hosts: someserver - roles: - - role: clickhouse_role -``` - -## Usage :runner: - -Look to the [defaults](defaults/main.yml) properties file to see the possible configuration properties, it is very likely that you will not need to override any variables but don't forget to set your Admin user 🦸 - -- [`main.yml`](./defaults/main.yml) for superset general purpose vars. - -### 👉 Don't forget - -- 🦸 To set your Admin user and use a secure 🔑 password. -- 📝 To set the `clickhouse_custom_config_file_path` and / or `clickhouse_custom_users_file_path` if you are going to use custom config files. - - 👉 See the [ClickHouse doc](https://clickhouse.com/docs/en/operations/configuration-files/). -- ☑️ To enable or disable using `clickhouse_role_manage_X` vars what things the role should manage. -- 📝 To set users, quotas, profiles, grants, databases to create. - - ℹ️ Or to unset if you want to DROP things. - - 👉 See the default molecule scenario [`group_vars`](./molecule/default/group_vars/clickhouse_group.yml) for more - -### ❗ You must know - -- ❗ To make us of the 'EXCEPT' clauses for [quota assignation](https://clickhouse.com/docs/en/sql-reference/statements/create/quota/) or [user grantees](https://clickhouse.com/docs/en/sql-reference/statements/create/user/#grantees) for example, you can add a minus or dash _( - )_ before the name. -- ❗ When setting `password_type` for users, it should be one of [this](https://clickhouse.com/docs/en/sql-reference/statements/create/user/#identification) -- ❗ When setting `keyed` for quota, it should be one of [this](https://clickhouse.com/docs/en/sql-reference/statements/create/quota/) -- ❗ In case you're using LDAP or Kerberos, set each with their own property `ldap_server` or 'kerberos' so `password_type` is not required -- ⚠️ `clickhouse_replicated_tables_macros` is deprecated, please use `clickhouse_macros` var - -#### Users and roles - -- ⚠️ Note that are two ways to set users for ClickHouse, `users.xml` or via SQL-query, to distinguish both methods note that in this role we use `clickhouse_custom_users_xml` and `clickhouse_custom_users` respectively (SQL recommended). -- ⚠️ When granting, you must know: - - When performing the GRANT actions to maintain the perms & privs clean a "general" REVOKE is performed before GRANTing - - There is an option to disable that before "GRANTS" clean up: `clickhouse_custom_grants_previous_cleanup` - - When granting permissions and privileges the order of the items in definition list takes precedence, is recommended to do this grant from less to the most restrictive. - - 👉 See example below, more at the default molecule scenario [`group_vars`](./molecule/default/group_vars/clickhouse_group.yml) for more. - - Statements Aliases are valid, but not handled at "ansible level" so this results in task making comparisions like `privileges: [DELETE]` vs `system.grants access_type = ALTER DELETE` (from ClickHouse), so we recommend set "un-aliased" perms and privs. - - When performing REVOKE or GRANT if a problem occurs may be result in unexpected / removed perms & privs in the ClickHouse DB ¡¡Be extra careful!! - - You can GRANT a role to a role, or roles to users with `clickhouse_custom_grant_roles`. - -##### Custom user definition example: - - ```yml - clickhouse_custom_users: - - user: - name: "Takumi" - password_type: plaintext_password - password: "AE86" - networks: - - "IP '::/0'" - settings: - - "max_memory_usage = 10000000000" - role: - - projectd_members - - tofu_shop - profile: - - default - grantees: - - projectd_members - quota: "default" - databases: [ProjectD] - # ldap_server: project.d - # kerberos: "" - - clickhouse_custom_grants: - - on: - databases: [Akina] - tables: ["*"] - privileges: [SELECT] - to: [initial_d] - - on: - # cluster: - databases: ["Akina"] - tables: [calendar, records] - # columns: [Notes] - privileges: [ALL] - to: [Takumi, Iketani] - with_grant_option: True - - on: - # cluster: - databases: ["Akina"] - tables: [records] - privileges: [SELECT, UPDATE] - to: [Iketani] - with_grant_option: False - - clickhouse_custom_grant_roles: - - roles: [initial_d] - to: [Takumi, Iketani] - # cluster: -``` - -## Testing :test_tube: - -### Install dependencies - -```sh -pipenv install -r test-requirements.txt -``` - -For more information read the [pipenv docs](ipenv-fork.readthedocs.io/en/latest/). - -### Testing - -```sh -$ pipenv run molecule test -``` - -## Built With :building_construction: - -![Ansible](https://img.shields.io/badge/ansible-4.10.0-green.svg) -![Molecule](https://img.shields.io/badge/molecule-3.5.2-green.svg) -![Docker](https://img.shields.io/badge/docker-5.0.3-blue.svg) -![Goss](https://img.shields.io/badge/goss-0.3.13-green.svg) - -## Versioning :card_file_box: - -For the versions available, see the [tags on this repository](https://github.com/idealista/clickhouse_role/tags). - -Additionaly you can see what change in each version in the [CHANGELOG.md](CHANGELOG.md) file. - -## Authors :superhero: - -- **Idealista** - _Work with_ - [idealista](https://github.com/idealista) - -See also the list of [contributors](https://github.com/idealista/clickhouse_role/contributors) who participated in this project. - -## License :spiral_notepad: - -![Apache 2.0 License](https://img.shields.io/hexpm/l/plug.svg) - -This project is licensed under the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) license - see the [LICENSE](LICENSE) file for details. - -## Contributing :construction_worker: - -Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. +- Removed what this repo doesn't use: the JDBC bridge, clickhouse-copier, SQL + databases, config/users files copied from the playbook, the `clickhouse_install_db` + toggle, Galaxy metadata and the standalone repo's scaffolding. diff --git a/ansible/roles/clickhouse/defaults/main.yml b/ansible/roles/clickhouse/defaults/main.yml index 1beeff4b..90695f93 100644 --- a/ansible/roles/clickhouse/defaults/main.yml +++ b/ansible/roles/clickhouse/defaults/main.yml @@ -4,13 +4,10 @@ # Version clickhouse_version: 23.8.9.54 clickhouse_release_type: lts # Values should be: stable, lts or testing -clickhouse_install_db: True -clickhouse_install_bridge: False # Clickhouse role management clickhouse_role_manage_users: False clickhouse_role_manage_roles: False -clickhouse_role_manage_databases: False clickhouse_role_manage_settings_profiles: False clickhouse_role_manage_quotas: False clickhouse_role_manage_grants: False @@ -81,10 +78,6 @@ clickhouse_tmp_directory: "{{ clickhouse_base_path }}/tmp" clickhouse_tmp_directory_mode: 0755 clickhouse_user_files_directory: "{{ clickhouse_base_path }}/user_files" clickhouse_access_control_directory: "{{ clickhouse_base_path }}/access/" -# clickhouse_custom_config_file_path: "{{ playbook_dir }}/config_files/config" -# clickhouse_custom_users_file_path: "{{ playbook_dir }}/config_files/users" -# clickhouse_custom_extra_config_file_path: "{{ playbook_dir }}/config_files/config/config.d" -# clickhouse_custom_extra_users_file_path: "{{ playbook_dir }}/config_files/users/users.d" # Logs clickhouse_logger: @@ -207,16 +200,6 @@ clickhouse_default_networks: clickhouse_http_server_default_response:
]]> -# Copier Configuration -clickhouse_copier_config_file_name: copier-config.xml -clickhouse_copier_config_directory: "{{ clickhouse_config_directory }}" -# clickhouse_copier_custom_config_file_path: "{{ playbook_dir }}/config_files/copier" -clickhouse_copier_logger_level: information -clickhouse_copier_logger_log: /var/log/clickhouse-server/clickhouse-copier.log -clickhouse_copier_logger_errorlog: /var/log/clickhouse-server/clickhouse-copier.err.log -clickhouse_copier_logger_size: 1000M -clickhouse_copier_logger_count: 10 - # GRPC # clickhouse_grpc_port: 9100 clickhouse_enable_ssl: false @@ -285,43 +268,6 @@ clickhouse_auth_backend: AD # principal: # realm: -# JDBC Bridge -clickhouse_jdbc_bridge_handler_on_config_change: "Restart-clickhouse-JDBC-bridge" -# clickhouse_jdbc_bridge_user: "{{ clickhouse_user }}" -# clickhouse_jdbc_bridge_group: "{{ clickhouse_group }}" -# clickhouse_jdbc_bridge_service: clickhouse-jdbc-bridge -# clickhouse_jdbc_bridge_service_template_path: jdbc-bridge/clickhouse-jdbc-bridge.service.j2 -# clickhouse_jdbc_bridge_service_state: started -# clickhouse_jdbc_bridge_service_enabled: True -# clickhouse_jdbc_bridge_config_path: "/etc/clickhouse-jdbc-bridge" -# clickhouse_jdbc_bridge_playbook_drivers_path: "{{ playbook_dir }}/files/jdbc-drivers" -# clickhouse_jdbc_bridge_drivers_path: "{{ clickhouse_base_path }}/drivers/" -# clickhouse_jdbc_bridge_documentation_link: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/blob/v{{ clickhouse_jdbc_bridge_version }}/README.md" -# clickhouse_jdbc_bridge_log_path: /var/log/clickhouse-jdbc-bridge -# clickhouse_jdbc_bridge_log_file: "{{ clickhouse_jdbc_bridge_log_path }}/clickhouse-jdbc-bridge.log" -# clickhouse_jdbc_bridge_log_level: DEBUG -# clickhouse_jdbc_bridge_env: | -# JVM_ARGS="-Xms1024m -Xmx2048m" -# clickhouse_jdbc_bridge_version: 2.1.0 -# clickhouse_jdbc_bridge_deb_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge_{{ clickhouse_jdbc_bridge_version }}-1_all.deb" -# clickhouse_jdbc_bridge_rpm_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge-{{ clickhouse_jdbc_bridge_version }}-1.noarch.rpm" -# clickhouse_jdbc_bridge: -# host: 127.0.0.1 -# port: 9019 -# clickhouse_jdbc_bridge_datasources_path: "{{ clickhouse_jdbc_bridge_config_path }}/config/datasources" - -# This is just an example other driver may require other settings -# clickhouse_jdbc_bridge_datasources: -# - name: clickhouse_test -# datasource: -# $schema: "../datasource.jschema" -# clickhouse: -# driverUrls: [] -# driverClassName: -# jdbcUrl: -# username: -# password: - # clickhouse_remote_servers: # - server: # servername: @@ -771,7 +717,7 @@ clickhouse_default_quotas: errors: 0 result_rows: 0 read_rows: 0 - execution_time: 0gv + execution_time: 0 # clickhouse_custom_quotas_xml: [] clickhouse_custom_quotas: [] @@ -779,14 +725,6 @@ clickhouse_custom_quotas: [] # Settings profiles clickhouse_custom_settings_profiles: [] -# Databases examples -clickhouse_custom_databases: [] -# clickhouse_custom_databases: -# - name: test1 -# - name: test2 -# - name: test3 - -# clickhouse_remote_servers: "fclickhouse_default_database" # For additional config, like clickhouse_config_xml_extra_config but in users.xml # clickhouse_user_xml_extra_config: | diff --git a/ansible/roles/clickhouse/handlers/main.yml b/ansible/roles/clickhouse/handlers/main.yml index 01e0ab18..67409399 100644 --- a/ansible/roles/clickhouse/handlers/main.yml +++ b/ansible/roles/clickhouse/handlers/main.yml @@ -9,15 +9,3 @@ systemd: name: "{{ clickhouse_service }}" state: reloaded - -- name: Restart-clickhouse-JDBC-bridge - systemd: - name: "{{ clickhouse_jdbc_bridge_service }}" - state: restarted - when: clickhouse_jdbc_bridge is defined - -- name: Reload-clickhouse-JDBC-bridge - systemd: - name: "{{ clickhouse_jdbc_bridge_service }}" - state: reloaded - when: clickhouse_jdbc_bridge is defined diff --git a/ansible/roles/clickhouse/logo.gif b/ansible/roles/clickhouse/logo.gif deleted file mode 100644 index d96f592f538fc16bd38c56d49773ce3c6580ddb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24279 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 z 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 K8;>kzKma?o`!g>9 diff --git a/ansible/roles/clickhouse/meta/main.yml b/ansible/roles/clickhouse/meta/main.yml deleted file mode 100644 index 06e5e050..00000000 --- a/ansible/roles/clickhouse/meta/main.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -galaxy_info: - role_name: clickhouse - author: idealista - company: Idealista S.A.U. - description: Clickhouse Role - min_ansible_version: "2.9.9" - license: Apache 2.0 - galaxy_tags: - - clickhouse - - db - - database - - install - platforms: - - name: Debian - versions: - - bullseye diff --git a/ansible/roles/clickhouse/molecule/default/Dockerfile.j2 b/ansible/roles/clickhouse/molecule/default/Dockerfile.j2 deleted file mode 100644 index 141d8fdf..00000000 --- a/ansible/roles/clickhouse/molecule/default/Dockerfile.j2 +++ /dev/null @@ -1,18 +0,0 @@ -# Molecule managed - -{% if item.registry is defined %} -FROM {{ item.registry.url }}/{{ item.image }} -{% else %} -FROM {{ item.image }} -{% endif %} - -# install minimal packages for debian slim images -{% if item.image == 'debian:bullseye-slim' %} -RUN apt-get update && \ - apt-get install -y python3 sudo bash ca-certificates iproute2 systemd systemd-sysv python3-pip && \ - apt-get clean -{% else %} -RUN apt-get update && \ - apt-get install -y python sudo bash ca-certificates iproute2 systemd systemd-sysv python-pip && \ - apt-get clean -{% endif %} diff --git a/ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml b/ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml deleted file mode 100644 index 53ceca26..00000000 --- a/ansible/roles/clickhouse/molecule/default/config_files/config/config.d/log_disable.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/ansible/roles/clickhouse/molecule/default/config_files/config/config.xml b/ansible/roles/clickhouse/molecule/default/config_files/config/config.xml deleted file mode 100644 index 2494a08c..00000000 --- a/ansible/roles/clickhouse/molecule/default/config_files/config/config.xml +++ /dev/null @@ -1,1257 +0,0 @@ - - - - - - - trace - /var/log/clickhouse-server/clickhouse-server.log - /var/log/clickhouse-server/clickhouse-server.err.log - - 1000M - 10 - - - - - - - - - - - - - - - - - - 8123 - - - 9000 - - - 9004 - - - 9005 - - - - - - - - - - - - 9009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4096 - - - 3 - - - - - false - - - /path/to/ssl_cert_file - /path/to/ssl_key_file - - - false - - - /path/to/ssl_ca_cert_file - - - deflate - - - medium - - - -1 - -1 - - - false - - - - - - - /etc/clickhouse-server/server.crt - /etc/clickhouse-server/server.key - - /etc/clickhouse-server/dhparam.pem - none - true - true - sslv2,sslv3 - true - - - - true - true - sslv2,sslv3 - true - - - - RejectCertificateHandler - - - - - - - - - 100 - - - 0 - - - - 10000 - - - 0.9 - - - 4194304 - - - 0 - - - - - - 8589934592 - - - 5368709120 - - - - 1000 - - - 134217728 - - - 10000 - - - /var/lib/clickhouse/ - - - /var/lib/clickhouse/tmp/ - - - - - - /var/lib/clickhouse/user_files/ - - - - - - - - - - - - - users.xml - - - - /var/lib/clickhouse/access/ - - - - - - - default - - - - - - - - - - - - default - - - - - - - - - true - - - false - - ' | sed -e 's|.*>\(.*\)<.*|\1|') - wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge_$PKG_VER-1_all.deb - apt install --no-install-recommends -f ./clickhouse-jdbc-bridge_$PKG_VER-1_all.deb - clickhouse-jdbc-bridge & - - * [CentOS/RHEL] - export MVN_URL=https://repo1.maven.org/maven2/ru/yandex/clickhouse/clickhouse-jdbc-bridge - export PKG_VER=$(curl -sL $MVN_URL/maven-metadata.xml | grep '' | sed -e 's|.*>\(.*\)<.*|\1|') - wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v$PKG_VER/clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm - yum localinstall -y clickhouse-jdbc-bridge-$PKG_VER-1.noarch.rpm - clickhouse-jdbc-bridge & - - Please refer to https://github.com/ClickHouse/clickhouse-jdbc-bridge#usage for more information. - ]]> - - - - - - - - - - - - - - - - localhost - 9000 - - - - - - - - - localhost - 9000 - - - - - localhost - 9000 - - - - - - - 127.0.0.1 - 9000 - - - - - 127.0.0.2 - 9000 - - - - - - true - - 127.0.0.1 - 9000 - - - - true - - 127.0.0.2 - 9000 - - - - - - - localhost - 9440 - 1 - - - - - - - localhost - 9000 - - - - - localhost - 1 - - - - - - - - - - - - - - - - - - - - - - - - 3600 - - - - 3600 - - - 60 - - - - - - - - - - - - - system - query_log
- - toYYYYMM(event_date) - - - - - - 7500 -
- - - - system - trace_log
- - toYYYYMM(event_date) - 7500 -
- - - - system - query_thread_log
- toYYYYMM(event_date) - 7500 -
- - - - system - query_views_log
- toYYYYMM(event_date) - 7500 -
- - - - system - part_log
- toYYYYMM(event_date) - 7500 -
- - - - - - system - metric_log
- 7500 - 1000 -
- - - - system - asynchronous_metric_log
- - 7000 -
- - - - - - engine MergeTree - partition by toYYYYMM(finish_date) - order by (finish_date, finish_time_us, trace_id) - - system - opentelemetry_span_log
- 7500 -
- - - - - system - crash_log
- - - 1000 -
- - - - system - session_log
- - toYYYYMM(event_date) - 7500 -
- - - - - - - - - - - - - - - - - - *_dictionary.xml - - - *_function.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /clickhouse/task_queue/ddl - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - click_cost - any - - 0 - 3600 - - - 86400 - 60 - - - - max - - 0 - 60 - - - 3600 - 300 - - - 86400 - 3600 - - - - - - /var/lib/clickhouse/format_schemas/ - - - - - hide encrypt/decrypt arguments - ((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\) - - \1(???) - - - - - - - - - - false - - false - - - https://6f33034cfe684dd7a3ab9875e57b1c8d@o388870.ingest.sentry.io/5226277 - - - - - - - -
diff --git a/ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml b/ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml deleted file mode 100644 index f24b19bd..00000000 --- a/ansible/roles/clickhouse/molecule/default/config_files/copier/copier-config.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - trace - /var/log/clickhouse-server/clickhouse-copier.log - /var/log/clickhouse-server/clickhouse-copier.err.log - 1000M - 10 - - - - zookeeper-test1 - 2181 - - - zookeeper-test2 - 2181 - - - zookeeper-test3 - 2181 - - - diff --git a/ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml b/ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml deleted file mode 100644 index ae6a6b94..00000000 --- a/ansible/roles/clickhouse/molecule/default/config_files/users/users.d/log_queries.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - 0 - 0 - - - \ No newline at end of file diff --git a/ansible/roles/clickhouse/molecule/default/config_files/users/users.xml b/ansible/roles/clickhouse/molecule/default/config_files/users/users.xml deleted file mode 100644 index f59f2832..00000000 --- a/ansible/roles/clickhouse/molecule/default/config_files/users/users.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - 10000000000 - - - random - - - - - 1 - - - - - - - - - - - - - ::/0 - - - - default - - - default - - - - - - - - - - - - - - 3600 - - - 0 - 0 - 0 - 0 - 0 - - - - diff --git a/ansible/roles/clickhouse/molecule/default/converge.yml b/ansible/roles/clickhouse/molecule/default/converge.yml deleted file mode 100644 index 2affbef0..00000000 --- a/ansible/roles/clickhouse/molecule/default/converge.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: Converge - hosts: clickhouse_group - roles: - - role: clickhouse_role diff --git a/ansible/roles/clickhouse/molecule/default/files/jdbc-drivers/file.drivers.here b/ansible/roles/clickhouse/molecule/default/files/jdbc-drivers/file.drivers.here deleted file mode 100644 index e69de29b..00000000 diff --git a/ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml b/ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml deleted file mode 100644 index 035d4195..00000000 --- a/ansible/roles/clickhouse/molecule/default/group_vars/clickhouse_group.yml +++ /dev/null @@ -1,226 +0,0 @@ ---- - -# Clickhouse role management -clickhouse_install_db: True -clickhouse_role_manage_users: True -clickhouse_role_manage_roles: True -clickhouse_role_manage_databases: True -clickhouse_role_manage_settings_profiles: True -clickhouse_role_manage_quotas: True -clickhouse_role_manage_grants: True - -clickhouse_private_tmp: false - -clickhouse_custom_extra_config_file_path: "{{ playbook_dir }}/config_files/config/config.d" -clickhouse_custom_extra_users_file_path: "{{ playbook_dir }}/config_files/users/users.d" - -# Users -clickhouse_admin_user: admin -clickhouse_admin_password: admin - -clickhouse_admin_users: - - user: - name: "{{ clickhouse_admin_user }}" - password: "{{ clickhouse_admin_password }}" - networks: "{{ clickhouse_default_networks }}" - profile: "default" - quota: "default" - access_management: 1 - named_collection_control: 1 - show_named_collections: 1 - show_named_collections_secrets: 1 - -clickhouse_custom_users_xml: - - user: - name: "Bunta" - password: "TofuShop" - networks: "{{ clickhouse_default_networks }}" - profile: "default" - quota: "default" - databases: [ex_racing] - -clickhouse_custom_users: - - user: - name: "Takumi" - password_type: PLAINTEXT_PASSWORD - password: "AE86" - networks: - - "IP '::/0'" - settings: - - "max_memory_usage = 10000000000" - role: - - projectd_members - - tofu_shop - profile: - - default - grantees: - - projectd_members - quota: "default" - databases: [ProjectD] - - user: - name: "Iketani" - password_type: PLAINTEXT_PASSWORD - password: "S13" - networks: - - "IP '::/0'" - - user: - name: "Ryosuke" - password_type: PLAINTEXT_PASSWORD - password: "PjD4ever" - networks: - - "IP '::/0'" - databases: [ProjectD] - grantees: - - projectd_members - - -tofu_shop - profile: - - default - - max_memory_usage_profile -# ldap_server: adf.com -# kerberos: "" - -# Roles -clickhouse_custom_roles: - - role: - name: projectd_members - settings: - - "max_memory_usage = 10000000000" - - role: - name: tofu_shop - settings: - - "max_memory_usage = 100" - - role: - name: initial_d - -clickhouse_custom_grants: - - on: - databases: [Akina] - tables: ["*"] - privileges: [SELECT] - to: [initial_d] - - on: - # cluster: - databases: ["racing"] - tables: ["*"] - # columns: ["*"] - privileges: [ALL] - to: [Takumi, Iketani] - - on: - # cluster: - databases: ["Akina"] - tables: [calendar, records] - # columns: [Notes] - privileges: [ALL] - to: [Takumi, Iketani] - with_grant_option: True - - on: - # cluster: - databases: ["Akina"] - tables: [records] - privileges: [SELECT, UPDATE] - to: [Iketani] - with_grant_option: False - -clickhouse_custom_grant_roles: - - roles: [initial_d] - to: [Takumi, Iketani] - # cluster: - -# Settings profiles -clickhouse_custom_settings_profiles: - - profile: - name: max_memory_usage_profile - settings: - - max_memory_usage = 100000001 - -# Quotas -clickhouse_custom_quotas: - - quota: - name: example_quota - # keyed: ip_address # One of this: user_name | ip_address | client_key | client_key,user_name | client_key,ip_address | NOT KEYED - settings: - - "INTERVAL 30 minute MAX execution_time = 0.5" - to: # Use a minus to use ALL EXCEPT e.g: -test_role - - tofu_shop - - quota: - name: example_quota_except - keyed: NOT KEYED - settings: - - "INTERVAL 300 minute MAX execution_time = 0.5" - to: - - -tofu_shop - -# Databases -clickhouse_custom_databases: - - name: ProjectD - engine: Atomic - comment: PjD_data - - name: racing - - name: ex_racing - -# Configuration -clickhouse_listen_hosts: - - 0.0.0.0 - -clickhouse_default_networks: - - '::/0' - -clickhouse_macros: - - macro: | - 1 - 1 - server: - - clickhouse - - macro: | - default - -# Copier Configuration -clickhouse_copier_logger_level: information -clickhouse_copier_logger_log: /var/log/clickhouse-server/clickhouse-copier.log -clickhouse_copier_logger_errorlog: /var/log/clickhouse-server/clickhouse-copier.err.log -clickhouse_copier_logger_size: 1000M -clickhouse_copier_logger_count: 10 -clickhouse_zookeeper: - - node: - host: zookeeper-test1 - port: 2181 - - node: - host: zookeeper-test2 - port: 2181 - - node: - host: zookeeper-test3 - port: 2181 - -# JDBC Bridge -clickhouse_jdbc_bridge_service: clickhouse-jdbc-bridge -clickhouse_jdbc_bridge_user: "{{ clickhouse_user }}" -clickhouse_jdbc_bridge_group: "{{ clickhouse_group }}" -clickhouse_jdbc_bridge_service_template_path: jdbc-bridge/clickhouse-jdbc-bridge.service.j2 -clickhouse_jdbc_bridge_service_state: started -clickhouse_jdbc_bridge_service_enabled: True -clickhouse_jdbc_bridge_config_path: "/etc/clickhouse-jdbc-bridge" -clickhouse_jdbc_bridge_file_drivers_path: "{{ playbook_dir }}/files/jdbc-drivers" -clickhouse_jdbc_bridge_drivers_path: "{{ clickhouse_base_path }}/drivers/" -clickhouse_jdbc_bridge_documentation_link: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/blob/v{{ clickhouse_jdbc_bridge_version }}/README.md" -clickhouse_jdbc_bridge_log_path: /var/log/clickhouse-jdbc-bridge -clickhouse_jdbc_bridge_log_file: "{{ clickhouse_jdbc_bridge_log_path }}/clickhouse-jdbc-bridge.log" -clickhouse_jdbc_bridge_log_level: DEBUG -clickhouse_jdbc_bridge_env: | - JVM_ARGS="-Xms1024m -Xmx2048m" -clickhouse_jdbc_bridge_version: 2.1.0 -clickhouse_jdbc_bridge_deb_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge_{{ clickhouse_jdbc_bridge_version }}-1_all.deb" -# clickhouse_jdbc_bridge_rpm_package: "https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v{{ clickhouse_jdbc_bridge_version }}/clickhouse-jdbc-bridge-{{ clickhouse_jdbc_bridge_version }}-1.noarch.rpm" -clickhouse_jdbc_bridge: - host: 127.0.0.1 - port: 9019 -clickhouse_jdbc_bridge_datasources_path: "{{ clickhouse_jdbc_bridge_config_path }}/config/datasources" -clickhouse_jdbc_bridge_datasources: - - name: clickhouse_test - datasource: - $schema: "../datasource.jschema" - clickhouse: - driverUrls: ["https://repo1.maven.org/maven2/com/clickhouse/clickhouse-jdbc/0.3.2-patch8/clickhouse-jdbc-0.3.2-patch8-http.jar"] - driverClassName: "com.clickhouse.jdbc.ClickHouseDriver" - jdbcUrl: "jdbc:clickhouse://localhost:8123/system?compress=false" - username: "{{ clickhouse_admin_user }}" - password: "{{ clickhouse_admin_password }}" diff --git a/ansible/roles/clickhouse/molecule/default/molecule.yml b/ansible/roles/clickhouse/molecule/default/molecule.yml deleted file mode 100644 index 6b95eb1e..00000000 --- a/ansible/roles/clickhouse/molecule/default/molecule.yml +++ /dev/null @@ -1,50 +0,0 @@ ---- -dependency: - name: galaxy - -driver: - name: docker - -lint: | - yamllint . - ansible-lint . - -platforms: - - name: clickhouse - groups: - - clickhouse_group - image: ${MOLECULE_DISTRO:-debian:bullseye-slim} - privileged: false - capabilities: - - SYS_ADMIN - tmpfs: - - /tmp - - /run - - /run/lock - volumes: - - '/sys/fs/cgroup:/sys/fs/cgroup:rw' - command: ${MOLECULE_DOCKER_COMMAND:-"/lib/systemd/systemd"} - cgroupns_mode: host - stop_signal: 'RTMIN+3' - exposed_ports: - - 8123/tcp - - 9000/tcp - - 9019/tcp - published_ports: - - 0.0.0.0:8123:8123/tcp - - 0.0.0.0:9000:9000/tcp - - 0.0.0.0:9019:9019/tcp - -provisioner: - name: ansible - lint: - name: ansible-lint - log: True - options: - vvv: True - -scenario: - name: default - -verifier: - name: ansible diff --git a/ansible/roles/clickhouse/molecule/default/tests/test_app.yml b/ansible/roles/clickhouse/molecule/default/tests/test_app.yml deleted file mode 100644 index e27aefff..00000000 --- a/ansible/roles/clickhouse/molecule/default/tests/test_app.yml +++ /dev/null @@ -1,98 +0,0 @@ ---- - -file: - {{ clickhouse_base_path }}: - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: directory - {{ clickhouse_config_directory }}: - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: directory - {{ clickhouse_log_directory }}: - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: directory - {{ clickhouse_tmp_directory }}: - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: directory - "{{ clickhouse_config_directory }}/{{ clickhouse_config_file_name }}": - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: file - "{{ clickhouse_config_directory }}/{{ clickhouse_users_file_name }}": - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: file - "{{ clickhouse_config_directory_config_d }}/log_disable.xml": - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: file - "{{ clickhouse_config_directory_users_d }}/log_queries.xml": - owner: {{ clickhouse_user }} - group: {{ clickhouse_group }} - exists: true - filetype: file - -service: - {{ clickhouse_service }}: - enabled: true - running: true - -port: - tcp:{{ clickhouse_tcp_port }}: - listening: true - -user: - {{ clickhouse_user }}: - exists: true - groups: - - {{ clickhouse_group }} - shell: /usr/sbin/nologin - -group: - {{ clickhouse_group }}: - exists: true - -command: - clickhouse-server_version: - exit-status: 0 - exec: "clickhouse-server --version" - stdout: - - "{{ clickhouse_version }}" - stderr: [] - timeout: 10000 - skip: false - clickhouse-client_version: - exit-status: 0 - exec: "clickhouse-client --version" - stdout: - - "{{ clickhouse_version }}" - stderr: [] - timeout: 10000 - skip: false - -http: - http://localhost:8123?query=SHOW+USERS: - status: 200 - timeout: 10000 - body: ["admin", "default", "readonly", "Bunta", "Takumi"] - username: {{ clickhouse_admin_user }} - password: {{ clickhouse_admin_password }} - skip: false - - http://localhost:8123?query=SHOW+DATABASES: - status: 200 - timeout: 10000 - body: ["system", "default", "ProjectD", "ex_racing", "racing"] - username: {{ clickhouse_admin_user }} - password: {{ clickhouse_admin_password }} - skip: false diff --git a/ansible/roles/clickhouse/molecule/default/verify.yml b/ansible/roles/clickhouse/molecule/default/verify.yml deleted file mode 100644 index dd574112..00000000 --- a/ansible/roles/clickhouse/molecule/default/verify.yml +++ /dev/null @@ -1,60 +0,0 @@ ---- -# This is an example playbook to execute goss tests. -# Tests need distributed to the appropriate ansible host/groups -# prior to execution by `goss validate`. - -- name: Verify clickhouse - hosts: - - clickhouse - become: true - vars: - goss_version: v0.3.13 - goss_arch: amd64 - goss_dst: /usr/local/bin/goss - goss_sha256sum: eb3522ff9682736ff61e2ad114de227de98debcf8a03ca66fcda3917577313e0 - goss_url: "https://github.com/aelsabbahy/goss/releases/download/{{ goss_version }}/goss-linux-{{ goss_arch }}" - goss_test_directory: /tmp - goss_format: documentation - - vars_files: - - ../../defaults/main.yml - - group_vars/clickhouse_group.yml - - tasks: - - name: Download and install Goss - get_url: - url: "{{ goss_url }}" - dest: "{{ goss_dst }}" - checksum: "sha256:{{ goss_sha256sum }}" - mode: 0755 - register: download_goss - until: download_goss is succeeded - retries: 3 - - - name: Copy Goss tests to remote - template: - src: "{{ item }}" - dest: "{{ goss_test_directory }}/{{ item | basename }}" - with_fileglob: - - "tests/test_*.yml" - - - name: Register test files - shell: "ls {{ goss_test_directory }}/test_*.yml" - register: test_files - - - name: Execute Goss tests - command: "{{ goss_dst }} -g {{ item }} validate --format {{ goss_format }}" - register: test_results - with_items: "{{ test_files.stdout_lines }}" - ignore_errors: true - - - name: Display details about the Goss results - debug: - msg: "{{ item.stdout_lines }}" - with_items: "{{ test_results.results }}" - - - name: Fail when tests fail - fail: - msg: "Goss failed to validate" - when: item.rc != 0 - with_items: "{{ test_results.results }}" diff --git a/ansible/roles/clickhouse/tasks/config/clickhouse.yml b/ansible/roles/clickhouse/tasks/config/clickhouse.yml index ec37eb4f..2e6f4109 100644 --- a/ansible/roles/clickhouse/tasks/config/clickhouse.yml +++ b/ansible/roles/clickhouse/tasks/config/clickhouse.yml @@ -1,65 +1,19 @@ --- -- name: CLICKHOUSE | Add configuration files (from vars) +- name: CLICKHOUSE | Configure server ansible.builtin.template: src: "{{ clickhouse_config_file_name }}.j2" dest: "{{ clickhouse_config_directory }}/{{ clickhouse_config_file_name }}" mode: 0644 - when: clickhouse_custom_config_file_path is not defined notify: "{{ clickhouse_handler_on_config_change }}" tags: clickhouse_configure -- name: CLICKHOUSE | Add configuration files (from user files) - ansible.builtin.copy: - src: "{{ clickhouse_custom_config_file_path }}/" - dest: "{{ clickhouse_config_directory }}" - owner: "{{ clickhouse_user }}" - group: "{{ clickhouse_group }}" - mode: 0644 - when: clickhouse_custom_config_file_path is defined - notify: "{{ clickhouse_handler_on_config_change }}" - tags: clickhouse_configure - -- name: CLICKHOUSE | Add extra configuration files - ansible.builtin.copy: - src: "{{ clickhouse_custom_extra_config_file_path }}/" - dest: "{{ clickhouse_config_directory_config_d }}/" - owner: "{{ clickhouse_user }}" - group: "{{ clickhouse_group }}" - mode: 0644 - when: clickhouse_custom_extra_config_file_path is defined - tags: clickhouse_configure - - name: CLICKHOUSE | Configure users (from vars) ansible.builtin.template: src: "{{ clickhouse_users_file_name }}.j2" dest: "{{ clickhouse_config_directory }}/{{ clickhouse_users_file_name }}" mode: 0644 - when: clickhouse_custom_users_file_path is not defined - notify: "{{ clickhouse_handler_on_config_change }}" - tags: - - clickhouse_configure - - clickhouse_users - -- name: CLICKHOUSE | Configure users files (from users files) - ansible.builtin.copy: - src: "{{ clickhouse_custom_users_file_path }}/" - dest: "{{ clickhouse_config_directory }}" - owner: "{{ clickhouse_user }}" - group: "{{ clickhouse_group }}" - mode: 0644 - when: clickhouse_custom_users_file_path is defined notify: "{{ clickhouse_handler_on_config_change }}" tags: - clickhouse_configure - clickhouse_users - -- name: CLICKHOUSE | Add extra user configuration files - ansible.builtin.copy: - src: "{{ clickhouse_custom_extra_users_file_path }}/" - dest: "{{ clickhouse_config_directory_users_d }}/" - owner: "{{ clickhouse_user }}" - group: "{{ clickhouse_group }}" - mode: 0644 - when: clickhouse_custom_extra_users_file_path is defined - tags: clickhouse_configure diff --git a/ansible/roles/clickhouse/tasks/config/copier.yml b/ansible/roles/clickhouse/tasks/config/copier.yml deleted file mode 100644 index 3613c943..00000000 --- a/ansible/roles/clickhouse/tasks/config/copier.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- - -- name: CLICKHOUSE | Add copier configuration files (from vars) - ansible.builtin.template: - src: "{{ clickhouse_copier_config_file_name }}.j2" - dest: "{{ clickhouse_config_directory }}/{{ clickhouse_copier_config_file_name }}" - mode: 0644 - when: clickhouse_copier_custom_config_file_path is not defined - notify: "{{ clickhouse_handler_on_config_change }}" - tags: clickhouse_configure_copier - -- name: CLICKHOUSE | Add copier configuration files (from user files) - ansible.builtin.copy: - src: "{{ clickhouse_copier_custom_config_file_path }}/" - dest: "{{ clickhouse_copier_config_directory }}" - owner: "{{ clickhouse_user }}" - group: "{{ clickhouse_group }}" - mode: 0644 - when: clickhouse_copier_custom_config_file_path is defined - notify: "{{ clickhouse_handler_on_config_change }}" - tags: clickhouse_configure_copier diff --git a/ansible/roles/clickhouse/tasks/config/databases.yml b/ansible/roles/clickhouse/tasks/config/databases.yml deleted file mode 100644 index ff1a665f..00000000 --- a/ansible/roles/clickhouse/tasks/config/databases.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- - -- name: CLICKHOUSE | Create default database (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q 'CREATE DATABASE IF NOT EXISTS `{{ clickhouse_default_database }}`'" # noqa 204 - changed_when: False - when: clickhouse_default_database is defined - no_log: True - tags: - - clickhouse_databases - - create - - SQL_driven - -- name: CLICKHOUSE | List databases (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.databases WHERE LOWER(name) NOT IN ('_temporary_and_external_tables', 'information_schema', 'default', 'system');\"" - changed_when: False - register: clickhouse_existing_sql_databases - no_log: True - tags: - - clickhouse_databases - - select - - SQL_driven - -- name: CLICKHOUSE | Drop databases (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP DATABASE IF EXISTS {{ item }} ;\"" - with_items: "{{ clickhouse_existing_sql_databases.stdout_lines }}" - when: - - clickhouse_existing_sql_databases is defined - - item not in clickhouse_custom_databases | map(attribute='name') | list - no_log: True - register: drop_databases - changed_when: drop_databases.rc != 0 - tags: - - clickhouse_databases - - delete - - SQL_driven - -- name: CLICKHOUSE | Create custom databases (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/DATABASE.j2') | quote }}" - with_items: "{{ clickhouse_custom_databases }}" - when: - - clickhouse_custom_databases is defined - - item.name not in clickhouse_existing_sql_databases.stdout_lines - no_log: True - register: add_databases - changed_when: add_databases.rc != 0 - tags: - - clickhouse_databases - - create - - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/main.yml b/ansible/roles/clickhouse/tasks/main.yml index dbd9322e..a23626dc 100644 --- a/ansible/roles/clickhouse/tasks/main.yml +++ b/ansible/roles/clickhouse/tasks/main.yml @@ -4,76 +4,43 @@ ansible.builtin.include_tasks: "install-{{ ansible_os_family | capitalize }}.yml" tags: - clickhouse_install - when: clickhouse_install_db is not defined or clickhouse_install_db - name: CLICKHOUSE | Configure clickhouse ansible.builtin.include_tasks: config/clickhouse.yml tags: - clickhouse_configure - when: clickhouse_install_db is not defined or clickhouse_install_db - -- name: CLICKHOUSE | Configure copier - ansible.builtin.include_tasks: config/copier.yml - tags: - - clickhouse_configure_copier - when: clickhouse_install_db is not defined or clickhouse_install_db - name: CLICKHOUSE | ClickHouse Service ansible.builtin.include_tasks: service/clickhouse.yml tags: - clickhouse_service - when: clickhouse_install_db is not defined or clickhouse_install_db - -- name: CLICKHOUSE | JDBC Bridge Service - ansible.builtin.include_tasks: service/jdbc-bridge-{{ ansible_distribution }}.yml - tags: - - clickhouse_jdbc_bridge_service - when: clickhouse_install_bridge - name: CLICKHOUSE | Configure settings profiles ansible.builtin.include_tasks: config/settings-profiles.yml tags: - clickhouse_settings_profiles - when: - - clickhouse_role_manage_settings_profiles - - clickhouse_install_db is not defined or clickhouse_install_db + when: clickhouse_role_manage_settings_profiles - name: CLICKHOUSE | Configure roles ansible.builtin.include_tasks: config/roles.yml tags: - clickhouse_roles - when: - - clickhouse_role_manage_roles - - clickhouse_install_db is not defined or clickhouse_install_db + when: clickhouse_role_manage_roles - name: CLICKHOUSE | Configure quotas ansible.builtin.include_tasks: config/quotas.yml tags: - clickhouse_quotas - when: - - clickhouse_role_manage_quotas - - clickhouse_install_db is not defined or clickhouse_install_db + when: clickhouse_role_manage_quotas - name: CLICKHOUSE | Configure users ansible.builtin.include_tasks: config/users.yml tags: - clickhouse_users - when: - - clickhouse_role_manage_users - - clickhouse_install_db is not defined or clickhouse_install_db - -- name: CLICKHOUSE | Configure databases - ansible.builtin.include_tasks: config/databases.yml - tags: - - clickhouse_databases - when: - - clickhouse_role_manage_databases - - clickhouse_install_db is not defined or clickhouse_install_db + when: clickhouse_role_manage_users - name: CLICKHOUSE | Configure perms & privs ansible.builtin.include_tasks: config/perms_and_privs.yml tags: - clickhouse_grants - when: - - clickhouse_role_manage_grants - - clickhouse_install_db is not defined or clickhouse_install_db + when: clickhouse_role_manage_grants diff --git a/ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml b/ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml deleted file mode 100644 index debb5bb1..00000000 --- a/ansible/roles/clickhouse/tasks/service/jdbc-bridge-Debian.yml +++ /dev/null @@ -1,103 +0,0 @@ ---- - -- name: CLICKHOUSE | Ensure clickhouse jdbc bridge group - ansible.builtin.group: - name: "{{ clickhouse_jdbc_bridge_group }}" - system: yes - state: present - -- name: CLICKHOUSE | Ensure clickhouse jdbc bridge user - ansible.builtin.user: - name: "{{ clickhouse_jdbc_bridge_user }}" - group: "{{ clickhouse_jdbc_bridge_group }}" - system: yes - shell: /usr/sbin/nologin - createhome: no - -- name: CLICKHOUSE | Ensure needed jdbc-bridge directories exist - ansible.builtin.file: - state: directory - dest: "{{ item }}" - owner: "{{ clickhouse_jdbc_bridge_user }}" - group: "{{ clickhouse_jdbc_bridge_group }}" - mode: 0750 - with_items: - - "{{ clickhouse_jdbc_bridge_config_path }}" - - "{{ clickhouse_jdbc_bridge_drivers_path }}" - - "{{ clickhouse_jdbc_bridge_datasources_path }}" - - "{{ clickhouse_jdbc_bridge_log_path }}" - become: true - tags: - - clickhouse_jdbc_bridge_service - -- name: CLICKHOUSE | Install Clickhouse JDBC Bridge if required - ansible.builtin.apt: - deb: "{{ clickhouse_jdbc_bridge_deb_package }}" - install_recommends: no - tags: - - clickhouse_jdbc_bridge_service - -- name: "CLICKHOUSE | Copy Clickhouse JDBC Bridge drivers from {{ clickhouse_jdbc_bridge_file_drivers_path }}" - ansible.builtin.copy: - src: "{{ item }}" - dest: "{{ clickhouse_jdbc_bridge_drivers_path }}" - owner: "{{ clickhouse_jdbc_bridge_user }}" - group: "{{ clickhouse_jdbc_bridge_group }}" - mode: 0640 - with_fileglob: - - "{{ clickhouse_jdbc_bridge_file_drivers_path }}/*" - tags: - - clickhouse_jdbc_bridge_service - -- name: CLICKHOUSE | Generate JDBC Bridge templates for datasources - ansible.builtin.template: - src: "jdbc-bridge/datasource.json.j2" - dest: "{{ clickhouse_jdbc_bridge_datasources_path }}/{{ item.name }}.json" - owner: "{{ clickhouse_jdbc_bridge_user }}" - group: "{{ clickhouse_jdbc_bridge_group }}" - mode: 0644 - with_items: "{{ clickhouse_jdbc_bridge_datasources }}" - become: true - notify: "{{ clickhouse_jdbc_bridge_handler_on_config_change }}" - tags: - - clickhouse_jdbc_bridge_service - -- name: CLICKHOUSE | Copy Clickhouse JDBC Bridge logging properties - ansible.builtin.template: - src: "jdbc-bridge/logging.properties.j2" - dest: "{{ clickhouse_jdbc_bridge_config_path }}/logging.properties" - owner: "{{ clickhouse_jdbc_bridge_user }}" - group: "{{ clickhouse_jdbc_bridge_group }}" - mode: 0644 - tags: - - clickhouse_jdbc_bridge_service - -- name: CLICKHOUSE | Copy Clickhouse JDBC Bridge env file - ansible.builtin.template: - src: "jdbc-bridge/set-env.sh.j2" - dest: "{{ clickhouse_jdbc_bridge_config_path }}/set-env.sh" - owner: "{{ clickhouse_jdbc_bridge_user }}" - group: "{{ clickhouse_jdbc_bridge_group }}" - mode: 0744 - tags: - - clickhouse_jdbc_bridge_service - -- name: CLICKHOUSE | Copy Clickhouse JDBC Bridge service - ansible.builtin.template: - src: "{{ clickhouse_jdbc_bridge_service_template_path }}" - dest: "/etc/systemd/system/{{ clickhouse_jdbc_bridge_service }}.service" - mode: 0644 - owner: root - group: root - tags: - - clickhouse_jdbc_bridge_service - -- name: CLICKHOUSE | Configuring Clickhouse JDBC Bridge service - ansible.builtin.systemd: - name: "{{ clickhouse_jdbc_bridge_service }}" - state: "{{ clickhouse_jdbc_bridge_service_state }}" - enabled: "{{ clickhouse_jdbc_bridge_service_enabled }}" - daemon_reload: yes - notify: "{{ clickhouse_jdbc_bridge_handler_on_config_change }}" - tags: - - clickhouse_jdbc_bridge_service diff --git a/ansible/roles/clickhouse/templates/DDL/DATABASE.j2 b/ansible/roles/clickhouse/templates/DDL/DATABASE.j2 deleted file mode 100644 index db58eab1..00000000 --- a/ansible/roles/clickhouse/templates/DDL/DATABASE.j2 +++ /dev/null @@ -1,5 +0,0 @@ -CREATE DATABASE IF NOT EXISTS {{ item.name }}{% if item.cluster is defined %} ON CLUSTER {{item.cluster }}{% endif %} -{% if item.engine is defined %} ENGINE = {{item.engine }}{% endif %} -{% if item.comment is defined %} COMMENT '{{item.comment }}'{% endif %} -; -{# https://clickhouse.com/docs/en/sql-reference/statements/create/role/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/copier-config.xml.j2 b/ansible/roles/clickhouse/templates/copier-config.xml.j2 deleted file mode 100644 index 66c0965c..00000000 --- a/ansible/roles/clickhouse/templates/copier-config.xml.j2 +++ /dev/null @@ -1,20 +0,0 @@ - - - - {{ clickhouse_copier_logger_level }} - {{ clickhouse_copier_logger_log }} - {{ clickhouse_copier_logger_errorlog }} - {{ clickhouse_copier_logger_size }} - {{ clickhouse_copier_logger_count }} - -{% if clickhouse_zookeeper is defined %} - -{% for node in clickhouse_zookeeper %} - - {{ node.host }} - {{ node.port }} - -{% endfor %} - -{% endif %} - \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 deleted file mode 100644 index ac7d3fc2..00000000 --- a/ansible/roles/clickhouse/templates/jdbc-bridge/clickhouse-jdbc-bridge.service.j2 +++ /dev/null @@ -1,26 +0,0 @@ -{{ ansible_managed | comment }} -[Unit] -Description=ClickHouse JDBC Bridge -Documentation={{ clickhouse_jdbc_bridge_documentation_link | default('') }} -Requires=network-online.target -After=network-online.target - -[Service] -PrivateTmp={{ clickhouse_private_tmp }} -Type=simple -User={{ clickhouse_jdbc_bridge_user }} -Group={{ clickhouse_jdbc_bridge_group }} -RuntimeDirectory={{ clickhouse_jdbc_bridge_service }} -LimitNOFILE={{ clickhouse_limit_no_file }} - -ExecStart=/usr/local/bin/clickhouse-jdbc-bridge -ExecReload=/bin/kill -HUP $MAINPID -KillSignal=SIGTERM -Restart=always -RestartSec=30 - -LimitCORE=infinity -CapabilityBoundingSet=CAP_NET_ADMIN CAP_IPC_LOCK CAP_SYS_NICE - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 deleted file mode 100644 index 05c9d1e4..00000000 --- a/ansible/roles/clickhouse/templates/jdbc-bridge/datasource.json.j2 +++ /dev/null @@ -1,6 +0,0 @@ -{# { -{%- for key, value in clickhouse_jdbc_bridge_datasources.items() recursive %} - "{{ key }}": {% if value is string %}"{{ value }}"{% elif value is mapping %}{{ "{" }}{{ loop(value.items()) | indent(4) }} - {{ "}" }}{% elif value is sequence %}{{ value | tojson }}{% else %}{{ value }}{% endif %}{{ "," if not loop.last else "" }}{% endfor %} -} #} -{{ item.datasource | to_json }} diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 deleted file mode 100644 index 3239cb81..00000000 --- a/ansible/roles/clickhouse/templates/jdbc-bridge/logging.properties.j2 +++ /dev/null @@ -1,14 +0,0 @@ -handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler - -java.util.logging.ConsoleHandler.level={{ clickhouse_jdbc_bridge_log_level }} -java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s %n - -java.util.logging.FileHandler.level={{ clickhouse_jdbc_bridge_log_level }} -java.util.logging.FileHandler.pattern={{ clickhouse_jdbc_bridge_log_file }} -java.util.logging.FileHandler.limit=10000000 -java.util.logging.FileHandler.count=5 -java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s %n - -.level={{ clickhouse_jdbc_bridge_log_level }} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 b/ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 deleted file mode 100644 index 2be77319..00000000 --- a/ansible/roles/clickhouse/templates/jdbc-bridge/set-env.sh.j2 +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -{% if clickhouse_jdbc_bridge_env is defined %} -{{ clickhouse_jdbc_bridge_env|indent(0, true) }} -{% endif %} \ No newline at end of file diff --git a/ansible/roles/clickhouse/test-requirements.txt b/ansible/roles/clickhouse/test-requirements.txt deleted file mode 100644 index 52e2f145..00000000 --- a/ansible/roles/clickhouse/test-requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -ansible==9.4.0 -molecule==24.2.0 -docker==7.0.0 -molecule-plugins[docker]==23.5.3 -ansible-lint==24.2.1 -yamllint==1.35.1 diff --git a/ansible/roles/oonidata_clickhouse/tasks/main.yml b/ansible/roles/oonidata_clickhouse/tasks/main.yml index 42754f02..75103fba 100644 --- a/ansible/roles/oonidata_clickhouse/tasks/main.yml +++ b/ansible/roles/oonidata_clickhouse/tasks/main.yml @@ -1,5 +1,5 @@ - ansible.builtin.include_role: - name: idealista.clickhouse_role + name: clickhouse tags: - oonidata - clickhouse diff --git a/docs/Runbooks.md b/docs/Runbooks.md index c1b14ffa..1a7ece6b 100644 --- a/docs/Runbooks.md +++ b/docs/Runbooks.md @@ -1074,7 +1074,7 @@ See the following links for more information: You should first create the replicated database cluster following the instructions at the [clickhouse docs](https://clickhouse.com/docs/en/architecture/replication). -The ooni-devops repo has a role called `oonidata_clickhouse` that does that by using the [idealista.clickhouse_role](https://github.com/idealista/clickhouse_role). +The ooni-devops repo has a role called `oonidata_clickhouse` that does that by using the `clickhouse` role (`ansible/roles/clickhouse`, vendored from [idealista/clickhouse_role](https://github.com/idealista/clickhouse_role)). Once the cluster is created you can proceed with creating a DATABASE on the cluster by running: From e05472e430f9003e8edf432933d613ca0f142a5b Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 03/16] clickhouse role: pin the apt repository key and the ClickHouse version Upstream added the repository key with the deprecated apt_key. It is now a signed-by keyring (from upstream PR #80), copied from files/clickhouse-repo-keyring.asc instead of fetched, so rotating it takes a reviewed commit (fingerprint 3A9E A119 3A97 B548 BE14 57D4 8919 F6BD 2B48 D754, checked 2026-09-22). The entry upstream added without signed-by is removed first: apt refuses the same repository with two different keys, and apt-get update fails on the whole host (checked on Debian 12). Upstream installed clickhouse_packages at clickhouse_version but didn't pin them, so `apt upgrade` installs the newest release in the repo: 26.8.36.3 where 24.8.6.70 is meant (checked on Debian 12 on 2026-10-27). The clickhouse-* packages are now pinned to clickhouse_version in /etc/apt/preferences.d/clickhouse, and the install allows downgrades, so a host that drifted goes back to it. To upgrade or downgrade, change clickhouse_version and rerun the role. --- ansible/roles/clickhouse/defaults/main.yml | 9 +- .../files/clickhouse-repo-keyring.asc | 93 +++++++++++++++++++ .../roles/clickhouse/tasks/install-Debian.yml | 42 ++++++++- 3 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 ansible/roles/clickhouse/files/clickhouse-repo-keyring.asc diff --git a/ansible/roles/clickhouse/defaults/main.yml b/ansible/roles/clickhouse/defaults/main.yml index 90695f93..48487f44 100644 --- a/ansible/roles/clickhouse/defaults/main.yml +++ b/ansible/roles/clickhouse/defaults/main.yml @@ -47,9 +47,12 @@ clickhouse_prerequisite_repos: - ca-certificates - dirmngr clickhouse_service: "clickhouse-server" -clickhouse_deb_repo: "deb https://packages.clickhouse.com/deb {{ clickhouse_release_type }} main" -clickhouse_deb_keyserver: hkp://keyserver.ubuntu.com:80 -clickhouse_deb_keyid: 8919F6BD2B48D754 +clickhouse_deb_repo: "deb [signed-by={{ clickhouse_deb_keyring_path }}] https://packages.clickhouse.com/deb {{ clickhouse_release_type }} main" +# The repo signing key is pinned (files/clickhouse-repo-keyring.asc), not fetched on +# every run; rotating it takes a reviewed commit. Verified 2026-09-22: fingerprint +# 3A9E A119 3A97 B548 BE14 57D4 8919 F6BD 2B48 D754, "ClickHouse Inc. Repositories Key +# ". +clickhouse_deb_keyring_path: /etc/apt/keyrings/clickhouse.asc clickhouse_packages: - procps - "clickhouse-client={{ clickhouse_version }}" diff --git a/ansible/roles/clickhouse/files/clickhouse-repo-keyring.asc b/ansible/roles/clickhouse/files/clickhouse-repo-keyring.asc new file mode 100644 index 00000000..c31c3f7d --- /dev/null +++ b/ansible/roles/clickhouse/files/clickhouse-repo-keyring.asc @@ -0,0 +1,93 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +e/vtNUUUDp3i8qczTjuaVywRbE3+9xjjsSiI +=c48w +-----END PGP PUBLIC KEY BLOCK----- diff --git a/ansible/roles/clickhouse/tasks/install-Debian.yml b/ansible/roles/clickhouse/tasks/install-Debian.yml index 47318a80..f0f59050 100644 --- a/ansible/roles/clickhouse/tasks/install-Debian.yml +++ b/ansible/roles/clickhouse/tasks/install-Debian.yml @@ -32,17 +32,48 @@ state: present tags: [clickhouse_install] -- name: CLICKHOUSE | Be sure key is present - ansible.builtin.apt_key: - keyserver: "{{ clickhouse_deb_keyserver }}" - id: "{{ clickhouse_deb_keyid }}" - state: present +- name: CLICKHOUSE | Ensure keyrings directory exists + ansible.builtin.file: + path: "{{ clickhouse_deb_keyring_path | dirname }}" + state: directory + mode: "0755" + tags: [clickhouse_install] + +- name: CLICKHOUSE | Install pinned apt repository key + ansible.builtin.copy: + src: clickhouse-repo-keyring.asc + dest: "{{ clickhouse_deb_keyring_path }}" + mode: "0644" + tags: [clickhouse_install] + +# The role used to add this repository without signed-by, in a file named after its URL. +# apt refuses the same repository with two different keys, so remove that entry first. +- name: CLICKHOUSE | Remove the apt repository entry without signed-by + ansible.builtin.apt_repository: + repo: "deb https://packages.clickhouse.com/deb {{ clickhouse_release_type }} main" + state: absent + update_cache: false tags: [clickhouse_install] - name: CLICKHOUSE | Add clickhouse apt repository (deb) ansible.builtin.apt_repository: repo: "{{ clickhouse_deb_repo }}" state: present + filename: clickhouse + tags: [clickhouse_install] + +# Without a pin, `apt upgrade` (or unattended-upgrades) moves ClickHouse to the +# newest release in the repo. To upgrade or downgrade, change clickhouse_version +# and rerun the role. +- name: CLICKHOUSE | Pin clickhouse packages to clickhouse_version + ansible.builtin.copy: + dest: /etc/apt/preferences.d/clickhouse + content: | + # Managed by ansible (clickhouse role): pins ClickHouse to clickhouse_version + Package: clickhouse-* + Pin: version {{ clickhouse_version }} + Pin-Priority: 1000 + mode: "0644" tags: [clickhouse_install] - name: CLICKHOUSE | Be sure clickhouse packages are installed (apt) @@ -50,6 +81,7 @@ name: "{{ clickhouse_packages }}" state: present update_cache: true + allow_downgrade: true tags: [clickhouse_install] - name: CLICKHOUSE | Ensure user files and access control directories exist From c22b6b7c9ed46e24bbaf062e7d04ea3a8dd49702 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 04/16] clickhouse: per-user settings, quotas and grants for the role's SQL templates Rewrites the SQL-driven users, quotas and grants in the format the next commit's templates read. The role as of this commit only creates users and quotas that don't exist yet, so this changes nothing on the existing nodes. - Each user carries its own settings, including readonly = 0, instead of the XML write/readonly profiles; without it a user gets the default profile's readonly = 2. The readonly profile refused log_comment for oonimeasurements on data3, which has failed every dev deploy since #1305. Its limits are CONST constraints instead, which apply on data1 and data2 for the first time (their users were created by hand with fewer limits). - The quotas were in the XML quota format, which the SQL template never read, so the role created them empty and unassigned; only data1's oonimeasurements quota was set up by hand. Its limits are now defined, but it stays unassigned (no `to`): the API's node, data2, has never had a quota, and the limits were never checked against its traffic. - The oonimeasurements role, which held nothing, is gone, and so are the keys the role never read (quota, databases) and the null user:/quota:/on: keys. - clickhouse_sql_objects_drop_unlisted is false until each node has been checked for SQL users and quotas not listed here. --- ansible/group_vars/clickhouse/vars.yml | 218 +++++++++---------------- 1 file changed, 75 insertions(+), 143 deletions(-) diff --git a/ansible/group_vars/clickhouse/vars.yml b/ansible/group_vars/clickhouse/vars.yml index 73275001..a8de0db9 100644 --- a/ansible/group_vars/clickhouse/vars.yml +++ b/ansible/group_vars/clickhouse/vars.yml @@ -164,7 +164,6 @@ clickhouse_distributed_ddl: cleanup_delay_period: 60 max_tasks_in_queue: 1000 -clickhouse_role_manage_settings_profiles: True clickhouse_default_profiles: default: readonly: 2 @@ -227,171 +226,104 @@ clickhouse_default_users: profile: write quota: default +# SQL-driven users, quotas and grants (see the clickhouse role's README.md). +# Each user carries its own settings and sets readonly = 0, since a user without +# it gets the default profile's readonly = 2; what it may do comes from +# clickhouse_custom_grants. clickhouse_role_manage_users: True +clickhouse_role_manage_quotas: True +clickhouse_role_manage_grants: True +# Off until each node has been checked for SQL users and quotas not listed here. +clickhouse_sql_objects_drop_unlisted: false + clickhouse_custom_users: - - user: - name: fastpath + - name: fastpath password_type: sha256_hash password: "{{ lookup('amazon.aws.aws_ssm', '/oonidevops/secrets/clickhouse_fastpath_password', profile='oonidevops_user_prod') | hash('sha256') }}" - networks: - - "IP '0.0.0.0/0'" - profile: - - write - quota: "fastpath" - databases: [ooni, oonitest] + networks: ["IP '0.0.0.0/0'"] + settings: + - "readonly = 0" + - "max_memory_usage = 61001001000" # 61 GB - - user: - name: oonimeasurements + # Only SELECT through its grants. CONST stops queries from raising its limits; + # other settings, such as log_comment, can still be set per query. + - name: oonimeasurements password_type: sha256_hash password: "{{ lookup('amazon.aws.aws_ssm', '/oonidevops/secrets/clickhouse_oonimeasurements_password', profile='oonidevops_user_prod') | hash('sha256') }}" - networks: - - "IP '0.0.0.0/0'" + networks: ["IP '0.0.0.0/0'"] settings: - # 1 GB - - "max_memory_usage = 1001001000" - # 60 seconds - - "max_execution_time = 30" - # 500 GB - - "max_bytes_to_read = 501001001000" - # 5 B - - "max_rows_to_read = 5001001000" - # 5s - - "timeout_before_checking_execution_speed = 5" - # 50k - - "max_result_rows = 51000" - profile: - - readonly - quota: oonimeasurements - databases: [ooni, oonitest] - - - user: - name: ooniprobe + - "readonly = 0" + - "max_memory_usage = 1001001000 CONST" # 1 GB + - "max_execution_time = 30 CONST" # seconds + - "max_bytes_to_read = 501001001000 CONST" # 500 GB + - "max_rows_to_read = 5001001000 CONST" # 5 B + - "timeout_before_checking_execution_speed = 5 CONST" # seconds + - "max_result_rows = 51000 CONST" + + - name: ooniprobe password_type: sha256_hash password: "{{ lookup('amazon.aws.aws_ssm', '/oonidevops/secrets/clickhouse_ooniprobe_password', profile='oonidevops_user_prod') | hash('sha256') }}" - networks: - - "IP '0.0.0.0/0'" - profile: - - write - quota: ooniprobe - databases: [ooni, oonitest] + networks: ["IP '0.0.0.0/0'"] + settings: + - "readonly = 0" + - "max_memory_usage = 61001001000" # 61 GB - - user: - name: oonirun + - name: oonirun password_type: sha256_hash password: "{{ lookup('amazon.aws.aws_ssm', '/oonidevops/secrets/clickhouse_oonirun_password', profile='oonidevops_user_prod') | hash('sha256') }}" - networks: - - "IP '0.0.0.0/0'" - profile: - - write - quota: oonirun - databases: [ooni, oonitest] + networks: ["IP '0.0.0.0/0'"] + settings: + - "readonly = 0" + - "max_memory_usage = 61001001000" # 61 GB - - user: - name: oonitestlists + - name: oonitestlists password_type: sha256_hash password: "{{ lookup('amazon.aws.aws_ssm', '/oonidevops/secrets/clickhouse_oonitestlists_password', profile='oonidevops_user_prod') | hash('sha256') }}" - networks: - - "IP '0.0.0.0/0'" - profile: - - write - quota: oonitestlists - databases: [ooni, oonitest] + networks: ["IP '0.0.0.0/0'"] + settings: + - "readonly = 0" + - "max_memory_usage = 61001001000" # 61 GB - - user: - name: notebook + - name: notebook password_type: sha256_hash password: "{{ lookup('amazon.aws.aws_ssm', '/oonidevops/secrets/clickhouse_notebook_password', profile='oonidevops_user_prod') | hash('sha256') }}" - networks: - - "IP '0.0.0.0/0'" - profile: - - write - quota: notebook - databases: [ooni] + networks: ["IP '0.0.0.0/0'"] + settings: + - "readonly = 0" + - "max_memory_usage = 61001001000" # 61 GB -clickhouse_role_manage_quotas: True clickhouse_custom_quotas: - - quota: - name: oonimeasurements - duration: 600 - queries: 12000 - errors: 1000 - result_rows: 0 - read_rows: 0 - execution_time: 1000 - - # no limits set - - quota: - name: ooniprobe - duration: 0 - queries: 0 - errors: 0 - result_rows: 0 - read_rows: 0 - execution_time: 0 - - # no limits set - - quota: - name: oonirun - duration: 0 - queries: 0 - errors: 0 - result_rows: 0 - read_rows: 0 - execution_time: 0 - - - quota: - name: oonitestlists - duration: 600 - queries: 12000 - errors: 1000 - result_rows: 0 - read_rows: 0 - execution_time: 1000 - - # no limits set - - quota: - name: notebook - duration: 0 - queries: 0 - errors: 0 - result_rows: 0 - read_rows: 0 - execution_time: 0 + # Not assigned to the user (no `to`), so each node keeps its current assignment: + # the API's node, data2, has never had a quota, and these limits were never + # checked against its traffic. + - name: oonimeasurements + intervals: + - "INTERVAL 600 second MAX queries = 12000, errors = 1000, execution_time = 1000" + # No limits: these exist on the nodes already, and are listed so that they aren't + # dropped if clickhouse_sql_objects_drop_unlisted is turned on + - name: ooniprobe + - name: oonirun + # TODO: the old vars had oonimeasurements' limits here too, never applied anywhere + - name: oonitestlists + - name: notebook -clickhouse_role_manage_grants: True clickhouse_custom_grants: -- on: - databases: [ooni, oonitest] - tables: ["*"] - privileges: [SELECT] - to: [ooniprobe, oonimeasurements, oonirun, fastpath, notebook] - -- on: - databases: [ooni, oonitest] - tables: [url_priorities] - privileges: [INSERT] - to: [oonitestlists] - -- on: - databases: [ooni, oonitest] - tables: [faulty_measurements] - privileges: [INSERT] - to: [ooniprobe] - -- on: - databases: [ooni, oonitest] - tables: [fastpath, obs_web, obs_openvpn, jsonl, new_jsonl] - privileges: [INSERT] - to: [fastpath] - -clickhouse_custom_grant_roles: - - roles: [oonimeasurements] - to: [oonimeasurements] - -clickhouse_role_manage_roles: True -clickhouse_custom_roles: - - role: - name: oonimeasurements + - databases: [ooni, oonitest] + tables: ["*"] + privileges: [SELECT] + to: [ooniprobe, oonimeasurements, oonirun, fastpath, notebook] + - databases: [ooni, oonitest] + tables: [url_priorities] + privileges: [INSERT] + to: [oonitestlists] + - databases: [ooni, oonitest] + tables: [faulty_measurements] + privileges: [INSERT] + to: [ooniprobe] + - databases: [ooni, oonitest] + tables: [fastpath, obs_web, obs_openvpn, jsonl, new_jsonl] + privileges: [INSERT] + to: [fastpath] clickhouse_default_quotas: - quota: From 65ed36d6942e1e8ec4c16df1ff648d92aa819778 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 05/16] clickhouse role: converge SQL users, quotas and grants, without hiding output - Existing objects were never updated: upstream only ran CREATE ... IF NOT EXISTS, and listed existing objects by storage 'local directory' while 24.8 says 'local_directory', so it never saw them. Users and quotas are now created if missing and ALTERed if they exist; ALTER keeps a user's grants and quota assignments. Users are applied before quotas, so that a quota's TO finds its user. Those not in vars are dropped only with clickhouse_sql_objects_drop_unlisted, false by default. - Grants started with REVOKE ALL ON *.* FROM ALL EXCEPT , so every SQL-driven user lost its privileges on every run until they were granted again (241 failed queries during two runs in a test with live traffic). Each grantee's privileges are now replaced in one GRANT ... WITH REPLACE OPTION. Roles granted by hand are left alone. - The admin password was on every command line, and all SQL task output was hidden with no_log. The admin credentials are now in a root-only clickhouse-client config file, the SQL goes in on stdin, and only the task writing that file has no_log. Each user's password is set in a statement of its own, so an error in its other settings doesn't quote the hash. - SQL roles and settings profiles, unused here, go, and so do LDAP, Kerberos, GRANTEES, DEFAULT ROLE and ON CLUSTER in CREATE USER: the templates cover the vars this repo uses (see README.md). Tested on three 24.8.6.70 nodes seeded like data1/data2/data3: users and quotas converge with their ids, grants and assignments kept, stray grants are removed, a second run changes nothing, and inserts and selects running throughout two role runs never failed. --- ansible/roles/clickhouse/README.md | 75 ++++++++++++++++++- ansible/roles/clickhouse/defaults/main.yml | 52 ++++++------- .../clickhouse/tasks/config/grants-roles.yml | 12 --- .../roles/clickhouse/tasks/config/grants.yml | 63 +++------------- .../tasks/config/perms_and_privs.yml | 41 ---------- .../roles/clickhouse/tasks/config/quotas.yml | 64 +++++++--------- .../roles/clickhouse/tasks/config/roles.yml | 39 ---------- .../tasks/config/settings-profiles.yml | 39 ---------- .../roles/clickhouse/tasks/config/users.yml | 65 ++++++++-------- ansible/roles/clickhouse/tasks/main.yml | 38 ++++++---- .../roles/clickhouse/templates/DDL/GRANT.j2 | 4 - .../roles/clickhouse/templates/DDL/GRANTS.j2 | 16 ++++ .../clickhouse/templates/DDL/GRANT_ROLE.j2 | 2 - .../roles/clickhouse/templates/DDL/QUOTA.j2 | 7 -- .../roles/clickhouse/templates/DDL/QUOTAS.j2 | 5 ++ .../roles/clickhouse/templates/DDL/REVOKE.j2 | 25 ------- .../roles/clickhouse/templates/DDL/ROLE.j2 | 5 -- .../templates/DDL/SETTINGS-PROFILE.j2 | 4 - .../roles/clickhouse/templates/DDL/USER.j2 | 10 --- .../roles/clickhouse/templates/DDL/USERS.j2 | 7 ++ .../clickhouse/templates/admin-client.xml.j2 | 5 ++ 21 files changed, 218 insertions(+), 360 deletions(-) delete mode 100644 ansible/roles/clickhouse/tasks/config/grants-roles.yml delete mode 100644 ansible/roles/clickhouse/tasks/config/perms_and_privs.yml delete mode 100644 ansible/roles/clickhouse/tasks/config/roles.yml delete mode 100644 ansible/roles/clickhouse/tasks/config/settings-profiles.yml delete mode 100644 ansible/roles/clickhouse/templates/DDL/GRANT.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/GRANTS.j2 delete mode 100644 ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 delete mode 100644 ansible/roles/clickhouse/templates/DDL/QUOTA.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/QUOTAS.j2 delete mode 100644 ansible/roles/clickhouse/templates/DDL/REVOKE.j2 delete mode 100644 ansible/roles/clickhouse/templates/DDL/ROLE.j2 delete mode 100644 ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 delete mode 100644 ansible/roles/clickhouse/templates/DDL/USER.j2 create mode 100644 ansible/roles/clickhouse/templates/DDL/USERS.j2 create mode 100644 ansible/roles/clickhouse/templates/admin-client.xml.j2 diff --git a/ansible/roles/clickhouse/README.md b/ansible/roles/clickhouse/README.md index 8735555d..e2c54400 100644 --- a/ansible/roles/clickhouse/README.md +++ b/ansible/roles/clickhouse/README.md @@ -1,6 +1,7 @@ # clickhouse -Installs and configures ClickHouse on Debian. +Installs and configures ClickHouse on Debian, and manages SQL-driven users, +quotas and grants. Vendored from [idealista/clickhouse_role](https://github.com/idealista/clickhouse_role) by Idealista S.A.U. and contributors, tag `3.5.2` @@ -9,8 +10,76 @@ under the Apache License 2.0, kept unchanged in `LICENSE`. OONI has modified the in this directory: the changes are listed below and in this repo's git history. Edit it here like any other role in this repo. +## What it does + +1. Installs ClickHouse `clickhouse_version` from packages.clickhouse.com, with the + repository key pinned in `files/clickhouse-repo-keyring.asc`, and pins the + `clickhouse-*` packages to that version in `/etc/apt/preferences.d/clickhouse`, so + `apt upgrade` can't move them (`tasks/install-Debian.yml`). To upgrade or + downgrade, change `clickhouse_version` and rerun the role. +2. Writes `config.xml` and `users.xml` from `templates/` (`tasks/config/clickhouse.yml`) + and the systemd unit (`tasks/service/clickhouse.yml`). +3. Applies `clickhouse_custom_users`, `clickhouse_custom_quotas` and + `clickhouse_custom_grants` as SQL (`tasks/config/{users,quotas,grants}.yml`, + `templates/DDL/`), each behind its `clickhouse_role_manage_*` toggle: + - users and quotas are created if missing and `ALTER`ed if they exist, so changes + in vars apply to existing objects too; `ALTER` keeps grants and assignments; + - each grantee's privileges are replaced in one `GRANT ... WITH REPLACE OPTION`, + so privileges it keeps are never revoked, even briefly; roles granted by hand are + left alone; + - users and quotas that aren't in vars are dropped when + `clickhouse_sql_objects_drop_unlisted` is true. + + The SQL runs through `clickhouse-client --config-file + {{ clickhouse_admin_client_config }}`, a root-only file with `clickhouse_admin_user`'s + credentials, and the statements go in on stdin, so task output isn't hidden. Only at + `-vvv` does ansible show the statements, and with them the users' password hashes. + ## Changes from upstream - Removed what this repo doesn't use: the JDBC bridge, clickhouse-copier, SQL - databases, config/users files copied from the playbook, the `clickhouse_install_db` - toggle, Galaxy metadata and the standalone repo's scaffolding. + databases, SQL roles and settings profiles, config/users files copied from the + playbook, the `clickhouse_install_db` toggle, LDAP/Kerberos/`GRANTEES`/`DEFAULT + ROLE` in `CREATE USER`, Galaxy metadata and the standalone repo's scaffolding. +- Existing SQL objects were never updated: upstream only ran `CREATE ... IF NOT + EXISTS`, and listed existing objects by `storage = 'local directory'` while + ClickHouse 24.8 says `local_directory`, so it never saw them. +- Upstream started its grants with `REVOKE ALL ON *.* FROM ALL EXCEPT `, + so every SQL-driven user lost its privileges on every run until they were granted + again. +- Upstream put `--password` on every command line and hid all SQL task output with + `no_log`. +- Upstream installed `clickhouse_packages` at `clickhouse_version` but didn't pin them, + so `apt upgrade` would install the newest release in the repo (26.8.36.3 instead of + 24.8.6.70, checked 2026-10-27). +- Debian 12+ apt keyring (`signed-by`, from upstream PR #80) with the key pinned; the + entry upstream added without it is removed, as apt refuses both. + +## Vars for SQL-driven objects + +```yaml +clickhouse_custom_users: + - name: user1 + password_type: sha256_hash + password: "" + networks: ["IP '10.0.0.0/8'"] # HOST clause, ANY when omitted + settings: # SETTINGS clause, NONE when omitted + - "readonly = 0" + - "max_execution_time = 30 CONST" + +clickhouse_custom_quotas: + - name: user1 + intervals: # FOR clause, limits kept when omitted + - "INTERVAL 600 second MAX queries = 12000" + to: [user1] # TO clause, assignment kept when omitted + +clickhouse_custom_grants: + - databases: [db1] + tables: ["*"] + privileges: [SELECT] + to: [user1] +``` + +XML users, profiles and quotas (`clickhouse_default_users`, `clickhouse_admin_users`, +`clickhouse_default_profiles`, `clickhouse_default_quotas`) go into `users.xml`; see +`templates/users.xml.j2`. diff --git a/ansible/roles/clickhouse/defaults/main.yml b/ansible/roles/clickhouse/defaults/main.yml index 48487f44..d8c8619f 100644 --- a/ansible/roles/clickhouse/defaults/main.yml +++ b/ansible/roles/clickhouse/defaults/main.yml @@ -5,12 +5,14 @@ clickhouse_version: 23.8.9.54 clickhouse_release_type: lts # Values should be: stable, lts or testing -# Clickhouse role management +# SQL-driven users, quotas and grants (tasks/config/{users,quotas,grants}.yml) clickhouse_role_manage_users: False -clickhouse_role_manage_roles: False -clickhouse_role_manage_settings_profiles: False clickhouse_role_manage_quotas: False clickhouse_role_manage_grants: False +# Drop SQL-driven users and quotas that exist on the server but aren't in vars +clickhouse_sql_objects_drop_unlisted: False +# clickhouse-client config holding the admin credentials for those tasks +clickhouse_admin_client_config: /etc/clickhouse-client/ansible-admin.xml ## Service options # Documentation @@ -271,6 +273,7 @@ clickhouse_auth_backend: AD # principal: # realm: + # clickhouse_remote_servers: # - server: # servername: @@ -685,31 +688,19 @@ clickhouse_default_users: clickhouse_custom_users: [] # clickhouse_custom_users: -# - user: -# name: "user2" -# password_type: PLAINTEXT_PASSWORD # one of: NO_PASSWORD, PLAINTEXT_PASSWORD, SHA256_PASSWORD, DOUBLE_SHA1_PASSWORD, LDAP, KERBEROS, SHA256_HASH, DOUBLE_SHA1_HASH -# password: "user2" -# networks: "{{ clickhouse_default_networks }}" -# profile: "default" -# quota: "default" -# databases: [test2] - -# Roles -clickhouse_custom_roles: [] +# - name: user1 +# password_type: sha256_hash +# password: "" +# networks: ["IP '10.0.0.0/8'"] # HOST clause; ANY when omitted +# settings: # SETTINGS clause; NONE when omitted +# - "readonly = 0" +# - "max_execution_time = 30 CONST" -# Grants -clickhouse_custom_grants_previous_cleanup: True # clickhouse_custom_grants: -# - on: -# cluster: -# databases: [] -# tables: [] -# columns: [] -# privileges: [] -# to: [] -# clickhouse_custom_grant_roles: -# - roles: [] -# to: [] +# - databases: [db1] +# tables: ["*"] +# privileges: [SELECT] +# to: [user1] # Quotas clickhouse_default_quotas: @@ -722,11 +713,12 @@ clickhouse_default_quotas: read_rows: 0 execution_time: 0 -# clickhouse_custom_quotas_xml: [] clickhouse_custom_quotas: [] - -# Settings profiles -clickhouse_custom_settings_profiles: [] +# clickhouse_custom_quotas: +# - name: user1 +# intervals: # FOR clause; limits are kept as they are when omitted +# - "INTERVAL 600 second MAX queries = 12000" +# to: [user1] # TO clause; assignment is kept as it is when omitted # For additional config, like clickhouse_config_xml_extra_config but in users.xml diff --git a/ansible/roles/clickhouse/tasks/config/grants-roles.yml b/ansible/roles/clickhouse/tasks/config/grants-roles.yml deleted file mode 100644 index 40f8d787..00000000 --- a/ansible/roles/clickhouse/tasks/config/grants-roles.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- - -- name: CLICKHOUSE | Assigning roles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} --multiquery -q {{ lookup('template', 'DDL/GRANT_ROLE.j2') | quote }}" - when: clickhouse_custom_grant_roles is defined - register: assigning_roles - changed_when: assigning_roles.rc != 0 - no_log: True - tags: - - clickhouse_grants - - grant - - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/grants.yml b/ansible/roles/clickhouse/tasks/config/grants.yml index d1a3dcd9..3a8af4ee 100644 --- a/ansible/roles/clickhouse/tasks/config/grants.yml +++ b/ansible/roles/clickhouse/tasks/config/grants.yml @@ -1,55 +1,12 @@ --- -- name: CLICKHOUSE | List perms and privs (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \" - SELECT user_name, role_name, - ifNull(database, '*') AS database , - ifNull(table, '*') AS table, - ifNull(column, '*') AS column, - arrayCompact(groupArray(access_type)) AS access_type, - grant_option - FROM system.grants - RIGHT JOIN system.users u ON user_name = u.name AND u.storage = 'local directory' - WHERE (user_name in ({{ '' ~ item.to ~ '' | join(', ') }}) or (role_name in ({{ '' ~ item.to ~ '' | join(', ') }}))) - AND database in ({{ '' ~ item.databases ~ '' | join(', ') }}) - AND table in ({{ '' ~ item.tables ~ '' | join(', ') }}) - GROUP BY user_name, role_name, database, table, column, grant_option - FORMAT JSON; - \"" - changed_when: False - register: clickhouse_existing_sql_grants - no_log: True - tags: - - clickhouse_grants - - select - - SQL_driven - -- name: CLICKHOUSE | Set var with the perms and privs listed - ansible.builtin.set_fact: - actual_privs: "{{ (clickhouse_existing_sql_grants.stdout | from_json)['data'] }}" - tags: - - clickhouse_grants - -# All logic of this revokes and grants are in the template because of "speed performance" instead of ansible task doing the loop -- name: CLICKHOUSE | Revoke user and roles grants missing (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} --multiquery -q {{ lookup('template', 'DDL/REVOKE.j2') | quote }}" - when: actual_privs is defined - register: revoke_grants_missing - changed_when: revoke_grants_missing.rc != 0 - no_log: True - tags: - - clickhouse_grants - - revoke - - SQL_driven - -- name: CLICKHOUSE | Do Grants for users and roles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} --multiquery -q {{ lookup('template', 'DDL/GRANT.j2') | quote }}" - when: - - item.privileges is defined - register: do_grants - changed_when: do_grants.rc != 0 - no_log: True - tags: - - clickhouse_grants - - grant - - SQL_driven +# Each grantee's privileges are replaced in one GRANT ... WITH REPLACE OPTION +# statement (templates/DDL/GRANTS.j2): privileges it keeps are never revoked, not +# even for a moment, and ones missing from vars go in the same statement. Reports changed +# on every run, as the statements are always sent. + +- name: CLICKHOUSE | Replace grants + ansible.builtin.command: + cmd: clickhouse-client --config-file {{ clickhouse_admin_client_config }} --multiquery + stdin: "{{ lookup('template', 'DDL/GRANTS.j2') }}" + tags: [clickhouse_grants] diff --git a/ansible/roles/clickhouse/tasks/config/perms_and_privs.yml b/ansible/roles/clickhouse/tasks/config/perms_and_privs.yml deleted file mode 100644 index 6fcd2bfe..00000000 --- a/ansible/roles/clickhouse/tasks/config/perms_and_privs.yml +++ /dev/null @@ -1,41 +0,0 @@ ---- - -- name: CLICKHOUSE | Get all LDAP users for grants cleaning (Optional step) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \" - SELECT name FROM system.users WHERE storage == 'ldap'; - \"" - register: ldap_user_list - changed_when: False - when: clickhouse_custom_grants_previous_cleanup - no_log: True - tags: - - clickhouse_grants - - select - - SQL_driven - -- name: CLICKHOUSE | Previous grants cleaning (Optional step) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \" - REVOKE ALL ON *.* FROM ALL EXCEPT {{ ((clickhouse_default_users | default([]) + clickhouse_admin_users | default([]) + clickhouse_custom_users_xml | default([])) | map(attribute='name') | list + (ldap_user_list.stdout_lines | default([]))) | join(', ') }}; - \"" - register: grants_clean - changed_when: grants_clean.rc != 0 - when: clickhouse_custom_grants_previous_cleanup - no_log: True - tags: - - clickhouse_grants - - select - - SQL_driven - -- name: CLICKHOUSE | Grant perms & privs - ansible.builtin.include_tasks: config/grants.yml - with_items: "{{ clickhouse_custom_grants }}" - when: clickhouse_custom_grants is defined - tags: - - clickhouse_grants - -- name: CLICKHOUSE | Grant roles - ansible.builtin.include_tasks: config/grants-roles.yml - with_items: "{{ clickhouse_custom_grant_roles }}" - when: clickhouse_custom_grant_roles is defined - tags: - - clickhouse_grants diff --git a/ansible/roles/clickhouse/tasks/config/quotas.yml b/ansible/roles/clickhouse/tasks/config/quotas.yml index 0dbdaa29..28011a34 100644 --- a/ansible/roles/clickhouse/tasks/config/quotas.yml +++ b/ansible/roles/clickhouse/tasks/config/quotas.yml @@ -1,39 +1,33 @@ --- -- name: CLICKHOUSE | List quotas (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.quotas WHERE storage == 'local directory';\"" - changed_when: False - register: clickhouse_existing_sql_quotas - no_log: True - tags: - - clickhouse_quotas - - select - - SQL_driven +# SQL-driven quotas (clickhouse_custom_quotas): created if missing, altered if they +# already exist. Without `to`, ALTER QUOTA leaves the quota's assignment as it is. -- name: CLICKHOUSE | Drop quotas (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP QUOTA IF EXISTS {{ item }} ;\"" - with_items: "{{ clickhouse_existing_sql_quotas.stdout_lines }}" - when: - - clickhouse_existing_sql_quotas is defined - - item not in clickhouse_custom_quotas | map(attribute='name') | list - no_log: True - register: drop_quotas - changed_when: drop_quotas.rc != 0 - tags: - - clickhouse_quotas - - delete - - SQL_driven +- name: CLICKHOUSE | List SQL quotas + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --query "SELECT name FROM system.quotas WHERE storage = 'local_directory'" + register: clickhouse_existing_quotas + changed_when: false + check_mode: false # read-only, so that --check can show what would change + tags: [clickhouse_quotas] -- name: CLICKHOUSE | Add quotas (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/QUOTA.j2') | quote }}" - with_items: "{{ clickhouse_custom_quotas }}" - when: - - clickhouse_custom_quotas is defined - - item.name not in clickhouse_existing_sql_quotas.stdout_lines - no_log: True - register: add_quotas - changed_when: add_quotas.rc != 0 - tags: - - clickhouse_quotas - - create - - SQL_driven +- name: CLICKHOUSE | Drop SQL quotas missing from vars + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --query "DROP QUOTA {{ item }}" + loop: "{{ clickhouse_existing_quotas.stdout_lines | difference(clickhouse_custom_quotas | map(attribute='name')) }}" + when: clickhouse_sql_objects_drop_unlisted + tags: [clickhouse_quotas] + +# Reports changed on every run, as the statements are always sent; they only change +# the server when the vars have changed. +- name: CLICKHOUSE | Create or update SQL quotas + ansible.builtin.command: + cmd: clickhouse-client --config-file {{ clickhouse_admin_client_config }} --multiquery + stdin: "{{ lookup('template', 'DDL/QUOTAS.j2') }}" + vars: + existing: "{{ clickhouse_existing_quotas.stdout_lines }}" + tags: [clickhouse_quotas] diff --git a/ansible/roles/clickhouse/tasks/config/roles.yml b/ansible/roles/clickhouse/tasks/config/roles.yml deleted file mode 100644 index 348a44e6..00000000 --- a/ansible/roles/clickhouse/tasks/config/roles.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- - -- name: CLICKHOUSE | List roles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.roles WHERE storage == 'local directory';\"" - changed_when: False - register: clickhouse_existing_sql_roles - no_log: True - tags: - - clickhouse_roles - - select - - SQL_driven - -- name: CLICKHOUSE | Drop roles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP ROLE IF EXISTS '{{ item }}' ;\"" - with_items: "{{ clickhouse_existing_sql_roles.stdout_lines }}" - when: - - clickhouse_existing_sql_roles is defined - - item not in clickhouse_custom_roles | map(attribute='name') | list - no_log: True - register: drop_roles - changed_when: drop_roles.rc != 0 - tags: - - clickhouse_roles - - delete - - SQL_driven - -- name: CLICKHOUSE | Add roles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/ROLE.j2') | quote }}" - with_items: "{{ clickhouse_custom_roles }}" - when: - - clickhouse_custom_roles is defined - - item.name not in clickhouse_existing_sql_roles.stdout_lines - no_log: True - register: add_roles - changed_when: add_roles.rc != 0 - tags: - - clickhouse_roles - - create - - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/settings-profiles.yml b/ansible/roles/clickhouse/tasks/config/settings-profiles.yml deleted file mode 100644 index 12e55934..00000000 --- a/ansible/roles/clickhouse/tasks/config/settings-profiles.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- - -- name: CLICKHOUSE | List settings profiles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.settings_profiles WHERE storage == 'local directory';\"" - changed_when: False - register: clickhouse_existing_sql_settings_profiles - no_log: True - tags: - - clickhouse_settings_profiles - - select - - SQL_driven - -- name: CLICKHOUSE | Drop settings-profiles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP SETTINGS PROFILE IF EXISTS {{ item }} ;\"" - with_items: "{{ clickhouse_existing_sql_settings_profiles.stdout_lines }}" - when: - - clickhouse_existing_sql_settings_profiles is defined - - item not in clickhouse_custom_settings_profiles | map(attribute='name') | list - no_log: True - register: drop_profiles - changed_when: drop_profiles.rc != 0 - tags: - - clickhouse_settings_profiles - - delete - - SQL_driven - -- name: CLICKHOUSE | Add settings profiles (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/SETTINGS-PROFILE.j2') | quote }}" - with_items: "{{ clickhouse_custom_settings_profiles }}" - when: - - clickhouse_custom_settings_profiles is defined - - item.name not in clickhouse_existing_sql_settings_profiles.stdout_lines - no_log: True - register: add_profiles - changed_when: add_profiles.rc != 0 - tags: - - clickhouse_settings_profiles - - create - - SQL_driven diff --git a/ansible/roles/clickhouse/tasks/config/users.yml b/ansible/roles/clickhouse/tasks/config/users.yml index 71c305e5..6893f504 100644 --- a/ansible/roles/clickhouse/tasks/config/users.yml +++ b/ansible/roles/clickhouse/tasks/config/users.yml @@ -1,39 +1,34 @@ --- -- name: CLICKHOUSE | List users (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"SELECT name FROM system.users WHERE storage == 'local directory';\"" - changed_when: False - register: clickhouse_existing_sql_users - no_log: True - tags: - - clickhouse_users - - select - - SQL_driven +# SQL-driven users (clickhouse_custom_users): created if missing, altered if they +# already exist, so that changes in vars always apply. ALTER keeps a user's grants +# and quota assignments. -- name: CLICKHOUSE | Drop users (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q \"DROP USER IF EXISTS '{{ item }}' ;\"" - with_items: "{{ clickhouse_existing_sql_users.stdout_lines }}" - when: - - clickhouse_existing_sql_users is defined - - item not in clickhouse_custom_users | map(attribute='name') | list - no_log: True - register: drop_users - changed_when: drop_users.rc != 0 - tags: - - clickhouse_users - - delete - - SQL_driven +- name: CLICKHOUSE | List SQL users + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --query "SELECT name FROM system.users WHERE storage = 'local_directory'" + register: clickhouse_existing_users + changed_when: false + check_mode: false # read-only, so that --check can show what would change + tags: [clickhouse_users] -- name: CLICKHOUSE | Add users (SQL-driven) - ansible.builtin.command: "clickhouse-client -u {{ clickhouse_admin_user }} --password {{ clickhouse_admin_password }} -q {{ lookup('template', 'DDL/USER.j2') | quote }}" - with_items: "{{ clickhouse_custom_users }}" - when: - - clickhouse_custom_users is defined - - item.name not in clickhouse_existing_sql_users.stdout_lines - no_log: True - register: add_users - changed_when: add_users.rc != 0 - tags: - - clickhouse_users - - create - - SQL_driven +- name: CLICKHOUSE | Drop SQL users missing from vars + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --query "DROP USER {{ item }}" + loop: "{{ clickhouse_existing_users.stdout_lines | difference(clickhouse_custom_users | map(attribute='name')) }}" + when: clickhouse_sql_objects_drop_unlisted + tags: [clickhouse_users] + +# Reports changed on every run, as the statements are always sent; they only change +# the server when the vars have changed. +- name: CLICKHOUSE | Create or update SQL users + ansible.builtin.command: + cmd: clickhouse-client --config-file {{ clickhouse_admin_client_config }} --multiquery + stdin: "{{ lookup('template', 'DDL/USERS.j2') }}" + vars: + existing: "{{ clickhouse_existing_users.stdout_lines }}" + tags: [clickhouse_users] diff --git a/ansible/roles/clickhouse/tasks/main.yml b/ansible/roles/clickhouse/tasks/main.yml index a23626dc..70d83923 100644 --- a/ansible/roles/clickhouse/tasks/main.yml +++ b/ansible/roles/clickhouse/tasks/main.yml @@ -15,17 +15,29 @@ tags: - clickhouse_service -- name: CLICKHOUSE | Configure settings profiles - ansible.builtin.include_tasks: config/settings-profiles.yml +# The SQL-driven users, quotas and grants below are +# applied with clickhouse-client as the admin user, which reads its credentials +# from this file, so that no password is on a command line or in task output. +- name: CLICKHOUSE | Admin client configuration + ansible.builtin.template: + src: admin-client.xml.j2 + dest: "{{ clickhouse_admin_client_config }}" + owner: root + group: root + mode: "0600" + no_log: true + diff: false + check_mode: false # the checks below read it, in --check too tags: - - clickhouse_settings_profiles - when: clickhouse_role_manage_settings_profiles + - clickhouse_quotas + - clickhouse_users + - clickhouse_grants -- name: CLICKHOUSE | Configure roles - ansible.builtin.include_tasks: config/roles.yml +- name: CLICKHOUSE | Configure users + ansible.builtin.include_tasks: config/users.yml tags: - - clickhouse_roles - when: clickhouse_role_manage_roles + - clickhouse_users + when: clickhouse_role_manage_users - name: CLICKHOUSE | Configure quotas ansible.builtin.include_tasks: config/quotas.yml @@ -33,14 +45,8 @@ - clickhouse_quotas when: clickhouse_role_manage_quotas -- name: CLICKHOUSE | Configure users - ansible.builtin.include_tasks: config/users.yml - tags: - - clickhouse_users - when: clickhouse_role_manage_users - -- name: CLICKHOUSE | Configure perms & privs - ansible.builtin.include_tasks: config/perms_and_privs.yml +- name: CLICKHOUSE | Configure grants + ansible.builtin.include_tasks: config/grants.yml tags: - clickhouse_grants when: clickhouse_role_manage_grants diff --git a/ansible/roles/clickhouse/templates/DDL/GRANT.j2 b/ansible/roles/clickhouse/templates/DDL/GRANT.j2 deleted file mode 100644 index f7d339d6..00000000 --- a/ansible/roles/clickhouse/templates/DDL/GRANT.j2 +++ /dev/null @@ -1,4 +0,0 @@ -{% for database in item.databases %}{% for table in item.tables %} -GRANT {% if item.cluster is defined %}ON CLUSTER {{ item.cluster }} {% endif %}{% for privilege in item.privileges %}{{ privilege }}{% if item.columns is defined %}({{ item.columns | join(', ') }}){% endif %}{{ ", " if not loop.last else "" }}{% endfor %} ON {{ database }}.{{ table }} TO {{ item.to | join(', ') }}{% if item.with_grant_option is defined and item.with_grant_option|bool %} WITH GRANT OPTION{% endif %}{% if item.with_replace_option is defined and item.with_replace_option|bool %} WITH REPLACE OPTION{% endif %}; -{% endfor %}{% endfor %} -{# https://clickhouse.com/docs/en/sql-reference/statements/grant #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/GRANTS.j2 b/ansible/roles/clickhouse/templates/DDL/GRANTS.j2 new file mode 100644 index 00000000..c50bf42f --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/GRANTS.j2 @@ -0,0 +1,16 @@ +{# One statement per grantee, with every privilege the clickhouse_custom_grants + entries give it ("PRIVILEGE ON database.table"), replacing all it had before. #} +{% set grantees = clickhouse_custom_grants | map(attribute='to') | flatten | unique %} +{% for grantee in grantees %} +{% set privileges = [] %} +{% for g in clickhouse_custom_grants if grantee in g.to %} +{% for database in g.databases %}{% for table in g.tables %}{% for privilege in g.privileges %} +{% set _ = privileges.append(privilege ~ ' ON ' ~ database ~ '.' ~ table) %} +{% endfor %}{% endfor %}{% endfor %} +{% endfor %} +GRANT {{ privileges | unique | join(', ') }} TO {{ grantee }} WITH REPLACE OPTION; +{% endfor %} +{# Users in vars that no entry grants anything to should have no privileges. #} +{% for user in clickhouse_custom_users | map(attribute='name') | reject('in', grantees) %} +REVOKE ALL ON *.* FROM {{ user }}; +{% endfor %} diff --git a/ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 b/ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 deleted file mode 100644 index edfae953..00000000 --- a/ansible/roles/clickhouse/templates/DDL/GRANT_ROLE.j2 +++ /dev/null @@ -1,2 +0,0 @@ -GRANT {% if item.cluster is defined %}ON CLUSTER {{ item.cluster }} {% endif %}{{ item.roles | join(', ') }} TO {{ item.to | join(', ') }}{% if item.with_admin_option is defined and item.with_admin_option|bool %} WITH ADMIN OPTION{% endif %}{% if item.with_replace_option is defined and item.with_replace_option|bool %} WITH REPLACE OPTION{% endif %}; -{# https://clickhouse.com/docs/en/sql-reference/statements/grant #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/QUOTA.j2 b/ansible/roles/clickhouse/templates/DDL/QUOTA.j2 deleted file mode 100644 index 5b5a5f13..00000000 --- a/ansible/roles/clickhouse/templates/DDL/QUOTA.j2 +++ /dev/null @@ -1,7 +0,0 @@ -CREATE QUOTA IF NOT EXISTS {{ item.name }}{% if item.cluster is defined %} ON CLUSTER {{item.cluster }}{% endif %} -{% if item.keyed is defined %}{% if item.keyed == 'NOT KEYED' %} {{ item.keyed }}{% else %} KEYED BY {{ item.keyed }}{% endif %}{% endif %} -{% if item.settings is defined %} FOR {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -{% if item.to is defined %} TO {% if item.to == 'ALL' %}{{ item.to }}{% elif item.to[0] is match('^[\-]') %}ALL EXCEPT {% for to in item.to %}{{ to | regex_replace('^[\-]', '') }}{{ ", " if not loop.last else "" }}{% endfor %} -{% else %}{% for to in item.to %}{{ to }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} {% endif %} -; -{# https://clickhouse.com/docs/en/sql-reference/statements/create/quota/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/QUOTAS.j2 b/ansible/roles/clickhouse/templates/DDL/QUOTAS.j2 new file mode 100644 index 00000000..8c518816 --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/QUOTAS.j2 @@ -0,0 +1,5 @@ +{# For each quota: CREATE if missing, otherwise ALTER. Without `intervals` or `to`, + ALTER leaves the quota's limits or assignment as they are. #} +{% for quota in clickhouse_custom_quotas %} +{{ 'ALTER' if quota.name in existing else 'CREATE' }} QUOTA {{ quota.name }}{% if quota.intervals is defined %} FOR {{ quota.intervals | join(', ') }}{% endif %}{% if quota.to is defined %} TO {{ quota.to | join(', ') }}{% endif %}; +{% endfor %} diff --git a/ansible/roles/clickhouse/templates/DDL/REVOKE.j2 b/ansible/roles/clickhouse/templates/DDL/REVOKE.j2 deleted file mode 100644 index c6f6f00b..00000000 --- a/ansible/roles/clickhouse/templates/DDL/REVOKE.j2 +++ /dev/null @@ -1,25 +0,0 @@ -{% for grant in actual_privs %} -{% for priv in grant.access_type %} -{% set revoke_condition = -(grant.user_name in item.to) or (grant.role_name in item.to) -and grant.database in item.databases -and grant.table in item.tables -and grant.column in item.columns|default('*') -and priv not in item.privileges -and grant.grant_option|bool != item.with_grant_option|default(False) --%} -{% if revoke_condition %} -REVOKE {{ priv }}{% if item.columns is defined %}({{ item.columns | join(', ') }}){% endif %} ON {{ grant.database }}.{{ grant.table }} FROM {{ grant.user_name }}; -{% endif %} -{% if grant.grant_option|bool and not item.with_grant_option|default(False) %} -REVOKE GRANT OPTION FOR {{ priv }} ON {{ grant.database }}.{{ grant.table }} FROM {{ grant.user_name }}; -{% endif %} -{% endfor %} -{% endfor %} -{# --------------- #} -{# REVOKE {{ row.1 }}{% if item.columns is defined %}({{ item.columns | join(', ') }}){% endif %} ON {{ row.0.database }}.{{ row.0.table }} FROM {{ row.0.user_name }}; - -{% if row.0.grant_option == 'True' and item.with_grant_option == 'False' %} -REVOKE GRANT OPTION FOR {{ row.1 }} ON {{ row.0.database }}.{{ row.0.table }} FROM {{ row.0.user_name }}; -{% endif %} #} -{# https://clickhouse.com/docs/en/sql-reference/statements/grant #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/ROLE.j2 b/ansible/roles/clickhouse/templates/DDL/ROLE.j2 deleted file mode 100644 index f58223da..00000000 --- a/ansible/roles/clickhouse/templates/DDL/ROLE.j2 +++ /dev/null @@ -1,5 +0,0 @@ -CREATE ROLE IF NOT EXISTS {{ item.name }} -{% if item.settings is defined %} SETTINGS {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -{% if item.profile is defined %} SETTINGS PROFILE {% for profile in item.profile %}'{{ profile }}'{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -; -{# https://clickhouse.com/docs/en/sql-reference/statements/create/role/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 b/ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 deleted file mode 100644 index 93bd4637..00000000 --- a/ansible/roles/clickhouse/templates/DDL/SETTINGS-PROFILE.j2 +++ /dev/null @@ -1,4 +0,0 @@ -CREATE SETTINGS PROFILE IF NOT EXISTS {{ item.name }} -{% if item.settings is defined %} SETTINGS {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -; -{# https://clickhouse.com/docs/en/sql-reference/statements/create/settings-profile/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/USER.j2 b/ansible/roles/clickhouse/templates/DDL/USER.j2 deleted file mode 100644 index d2fe3e5e..00000000 --- a/ansible/roles/clickhouse/templates/DDL/USER.j2 +++ /dev/null @@ -1,10 +0,0 @@ -CREATE USER IF NOT EXISTS '{{ item.name }}'{% if item.cluster is defined %} ON CLUSTER {{item.cluster }}{% endif %} -{% if item.ldap_server is defined %} IDENTIFIED WITH ldap SERVER '{{ item.ldap_server }}'{% elif item.kerberos is defined %} IDENTIFIED WITH kerberos {% if item.kerberos != '' %}REALM '{{ item.kerberos }}'{% endif %}{% elif item.password is defined %} IDENTIFIED WITH {{ item.password_type | default("sha256_password") | lower }}{% if item.password is defined and item.password_type is not defined or (item.password is defined and item.password_type | upper != 'NO_PASSWORD') %} BY '{{ item.password}}'{% endif %}{% else %} NOT IDENTIFIED{% endif %} -{% if item.networks is defined %} HOST {% for host in item.networks %}{{ host }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -{% if item.role is defined %} DEFAULT ROLE {% for role in item.role %}{{ role }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -{% if item.default_database is defined %}DEFAULT DATABASE {{ item.default_database }}{% endif %} -{% if item.grantees is defined %} GRANTEES {% for grantee in item.grantees %}{% if grantee is not match('^[\-]') %}{{ ", " if not loop.first and grantee is not match('^[\-]') else "" }}{{ grantee }}{% endif %}{% endfor %}{% if item.grantees | map('regex_search', '^[\-]') | select('string') | list| length > 0 %} EXCEPT {% endif %}{% for exception in item.grantees | map('regex_search', '^[\-].*') | select('string') | list %}{{ exception | regex_replace('^[\-]', '')}}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -{% if item.settings is defined %} SETTINGS {% for setting in item.settings %}{{ setting }}{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -{% if item.profile is defined %} SETTINGS PROFILE {% for profile in item.profile %}'{{ profile }}'{{ ", " if not loop.last else "" }}{% endfor %}{% endif %} -; -{# https://clickhouse.com/docs/en/sql-reference/statements/create/user/ #} \ No newline at end of file diff --git a/ansible/roles/clickhouse/templates/DDL/USERS.j2 b/ansible/roles/clickhouse/templates/DDL/USERS.j2 new file mode 100644 index 00000000..f402fa44 --- /dev/null +++ b/ansible/roles/clickhouse/templates/DDL/USERS.j2 @@ -0,0 +1,7 @@ +{# For each user: CREATE if missing, otherwise ALTER, which keeps its grants and quota + assignments. The password is set in a statement of its own, so that a mistake in the + other clauses fails without ClickHouse quoting the password hash in its error. #} +{% for user in clickhouse_custom_users %} +{{ 'ALTER' if user.name in existing else 'CREATE' }} USER {{ user.name }} IDENTIFIED WITH {{ user.password_type }} BY '{{ user.password }}'; +ALTER USER {{ user.name }} HOST {{ user.networks | join(', ') if user.networks is defined else 'ANY' }} SETTINGS {{ user.settings | join(', ') if user.settings is defined else 'NONE' }}; +{% endfor %} diff --git a/ansible/roles/clickhouse/templates/admin-client.xml.j2 b/ansible/roles/clickhouse/templates/admin-client.xml.j2 new file mode 100644 index 00000000..ccd02b6d --- /dev/null +++ b/ansible/roles/clickhouse/templates/admin-client.xml.j2 @@ -0,0 +1,5 @@ + + + {{ clickhouse_admin_user }} + {{ clickhouse_admin_password }} + From 4fd0ee1024f47564b3c61289ef2bc136dd2f6c15 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 06/16] deploy-clickhouse: deploy one host at a time The role restarts ClickHouse when its config changes, and the play ran on all hosts at once, so a config change restarted data1-3, which are also the Keeper ensemble, together. With serial: 1 one node restarts at a time, and the play stops at the first host that fails. --- ansible/deploy-clickhouse.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ansible/deploy-clickhouse.yml b/ansible/deploy-clickhouse.yml index 630d1736..3477006a 100644 --- a/ansible/deploy-clickhouse.yml +++ b/ansible/deploy-clickhouse.yml @@ -6,6 +6,9 @@ - data2.htz-fsn.prod.ooni.nu - data3.htz-fsn.prod.ooni.nu become: true + # One host at a time: a config change restarts ClickHouse, and data1-3 are + # also the Keeper ensemble, so restarting them together loses quorum. + serial: 1 roles: - role: prometheus_node_exporter tags: [node_exporter] From 845f183a65fab63b254f1e192291b99ee14c7865 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 07/16] clickhouse role: test the SQL users, quotas and grants, and the apt pin tests/run.sh, also run in CI when the role or group_vars/clickhouse/vars.yml change, checks what the role's README says it does, against ClickHouse 24.8.6.70 with the real vars (secrets replaced by test values) and a users.xml rendered from the role's template: - test_sql_objects.py runs the role's SQL tasks on an empty node and three nodes seeded like production's data1-3, and checks that every node ends up like the empty one; that each user logs in with its password from vars (an old one is refused), can do what its grants allow and nothing else (including on a database no one is granted), and has its settings, CONST ones fixed; that ids, quota assignments, roles granted by hand and objects not in vars are kept, and that users and quotas not in vars are dropped when clickhouse_sql_objects_drop_unlisted is set; that a second run changes nothing; that the role sends no REVOKE, and queries running during the role never fail; and that the output, at default verbosity, shows errors but no password or hash. - apt_pin.sh checks on Debian 12, against packages.clickhouse.com, that with the role's pin file apt installs the pinned version and not the newest release (and with an older pin too, when clickhouse_version is the newest). --- .github/workflows/test_clickhouse_role.yml | 27 ++ ansible/roles/clickhouse/tests/.gitignore | 1 + ansible/roles/clickhouse/tests/README.md | 21 ++ ansible/roles/clickhouse/tests/apt_pin.sh | 37 +++ .../clickhouse/tests/bin/clickhouse-client | 7 + ansible/roles/clickhouse/tests/compose.yml | 16 + ansible/roles/clickhouse/tests/play.yml | 17 + ansible/roles/clickhouse/tests/render.yml | 7 + ansible/roles/clickhouse/tests/run.sh | 16 + ansible/roles/clickhouse/tests/seed/data1.sql | 22 ++ ansible/roles/clickhouse/tests/seed/data2.sql | 10 + ansible/roles/clickhouse/tests/seed/data3.sql | 9 + .../clickhouse/tests/test_sql_objects.py | 299 ++++++++++++++++++ 13 files changed, 489 insertions(+) create mode 100644 .github/workflows/test_clickhouse_role.yml create mode 100644 ansible/roles/clickhouse/tests/.gitignore create mode 100644 ansible/roles/clickhouse/tests/README.md create mode 100755 ansible/roles/clickhouse/tests/apt_pin.sh create mode 100755 ansible/roles/clickhouse/tests/bin/clickhouse-client create mode 100644 ansible/roles/clickhouse/tests/compose.yml create mode 100644 ansible/roles/clickhouse/tests/play.yml create mode 100644 ansible/roles/clickhouse/tests/render.yml create mode 100755 ansible/roles/clickhouse/tests/run.sh create mode 100644 ansible/roles/clickhouse/tests/seed/data1.sql create mode 100644 ansible/roles/clickhouse/tests/seed/data2.sql create mode 100644 ansible/roles/clickhouse/tests/seed/data3.sql create mode 100755 ansible/roles/clickhouse/tests/test_sql_objects.py diff --git a/.github/workflows/test_clickhouse_role.yml b/.github/workflows/test_clickhouse_role.yml new file mode 100644 index 00000000..203ce3ce --- /dev/null +++ b/.github/workflows/test_clickhouse_role.yml @@ -0,0 +1,27 @@ +# Tests the clickhouse role's SQL users, quotas and grants and its apt pin +# (ansible/roles/clickhouse/tests) when the role or its vars change +name: Test ClickHouse role +on: + pull_request: + paths: + - "ansible/roles/clickhouse/**" + - "ansible/group_vars/clickhouse/vars.yml" + - ".github/workflows/test_clickhouse_role.yml" + push: + branches: [main] + paths: + - "ansible/roles/clickhouse/**" + - "ansible/group_vars/clickhouse/vars.yml" + - ".github/workflows/test_clickhouse_role.yml" + +jobs: + sql-objects: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - run: pipx install ansible-core==2.17.7 && sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends python3-yaml + - run: ansible/roles/clickhouse/tests/run.sh + - if: failure() + uses: actions/upload-artifact@v4 + with: {name: sql-objects-out, path: ansible/roles/clickhouse/tests/out/} diff --git a/ansible/roles/clickhouse/tests/.gitignore b/ansible/roles/clickhouse/tests/.gitignore new file mode 100644 index 00000000..89f9ac04 --- /dev/null +++ b/ansible/roles/clickhouse/tests/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/ansible/roles/clickhouse/tests/README.md b/ansible/roles/clickhouse/tests/README.md new file mode 100644 index 00000000..c2f008c9 --- /dev/null +++ b/ansible/roles/clickhouse/tests/README.md @@ -0,0 +1,21 @@ +# Tests + +`run.sh` checks what the role's README says it does, against ClickHouse 24.8.6.70 +(production's version) in containers, using the real +`ansible/group_vars/clickhouse/vars.yml` with its secrets replaced by test values: + +- `test_sql_objects.py` runs the role's SQL tasks against an empty node and three + nodes seeded like production's data1-3 (`seed/`), and checks that every node ends up + like the empty one; that each user logs in with its password from vars, can do what + its grants allow and nothing else, and has its settings; that ids, quota assignments, + roles granted by hand and objects not in vars are kept, and the objects dropped with + `clickhouse_sql_objects_drop_unlisted`; + that a second run changes nothing; that queries running during the role never fail; + and that task output shows errors but no password or hash. +- `apt_pin.sh` checks, on Debian 12 against packages.clickhouse.com, that with the + role's pin file apt installs `clickhouse_version` and not the newest release. + +Needs docker compose (or rootless podman with +`DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock`), ansible-playbook and PyYAML. +`KEEP=1` leaves the containers running; `SKIP_APT_PIN=1` skips the apt test. The +ansible output of each run is in `out/`. diff --git a/ansible/roles/clickhouse/tests/apt_pin.sh b/ansible/roles/clickhouse/tests/apt_pin.sh new file mode 100755 index 00000000..8245c838 --- /dev/null +++ b/ansible/roles/clickhouse/tests/apt_pin.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# With the role's pin file, apt's candidate for the ClickHouse packages from +# packages.clickhouse.com is the pinned version, not the newest release in the repo. When +# clickhouse_version is the newest, a pin to an older release is checked too, as the pin +# can't show otherwise. +set -euo pipefail +cd "$(dirname "$0")" +vars=../../../group_vars/clickhouse/vars.yml +version=$(sed -n 's/^clickhouse_version: *//p' $vars) +release=$(sed -n 's/^clickhouse_release_type: *//p' $vars); release=${release:-lts} +# the pin file as tasks/install-Debian.yml writes it, for a version given later +pin=$(python3 -c ' +import yaml +task = next(t for t in yaml.safe_load(open("../tasks/install-Debian.yml")) if t.get("ansible.builtin.copy", {}).get("dest") == "/etc/apt/preferences.d/clickhouse") +print(task["ansible.builtin.copy"]["content"], end="") +') +docker run --rm -i -v "$PWD/..:/role:ro,z" -e VERSION="$version" -e RELEASE="$release" -e PIN="$pin" docker.io/library/debian:12 bash -s <<'SH' +set -euo pipefail +apt-get update -qq >/dev/null && apt-get install -y -qq ca-certificates >/dev/null +mkdir -p /etc/apt/keyrings && cp /role/files/clickhouse-repo-keyring.asc /etc/apt/keyrings/clickhouse.asc +echo "deb [signed-by=/etc/apt/keyrings/clickhouse.asc] https://packages.clickhouse.com/deb $RELEASE main" > /etc/apt/sources.list.d/clickhouse.list +apt-get update -qq >/dev/null +newest=$(apt-cache policy clickhouse-server | sed -n 's/^ Candidate: //p') +versions=("$VERSION") +if [ "$newest" = "$VERSION" ]; then + versions+=("$(apt-cache madison clickhouse-server | awk '{print $3}' | sed -n 2p)") +fi +fail=0 +for v in "${versions[@]}"; do + printf "%s" "${PIN//\{\{ clickhouse_version \}\}/$v}" > /etc/apt/preferences.d/clickhouse + for p in clickhouse-server clickhouse-client clickhouse-common-static; do + c=$(apt-cache policy $p | sed -n 's/^ Candidate: //p') + if [ "$c" = "$v" ]; then echo "ok pinned to $v, $p: candidate $c (newest in the repo: $newest)"; else echo "FAIL pinned to $v, $p: candidate $c"; fail=1; fi + done +done +exit $fail +SH diff --git a/ansible/roles/clickhouse/tests/bin/clickhouse-client b/ansible/roles/clickhouse/tests/bin/clickhouse-client new file mode 100755 index 00000000..f1238dd7 --- /dev/null +++ b/ansible/roles/clickhouse/tests/bin/clickhouse-client @@ -0,0 +1,7 @@ +#!/bin/bash +# The role runs clickhouse-client on the ClickHouse host; run it in this test host's +# container instead (CH_NODE), where tests/out/ is mounted at /out. +set -euo pipefail +args=() +for a in "$@"; do args+=("${a/#$TESTS\/out\//\/out\/}"); done +exec $COMPOSE -f "$TESTS/compose.yml" exec -T "$CH_NODE" clickhouse-client "${args[@]}" diff --git a/ansible/roles/clickhouse/tests/compose.yml b/ansible/roles/clickhouse/tests/compose.yml new file mode 100644 index 00000000..cc889b91 --- /dev/null +++ b/ansible/roles/clickhouse/tests/compose.yml @@ -0,0 +1,16 @@ +# ClickHouse at production's version: `fresh` starts empty, data1-3 are seeded like the +# production nodes (seed/). users.xml is rendered from the role's template (render.yml); +# out/ also holds the admin client configs the role's tasks write. +x-node: &node + image: docker.io/clickhouse/clickhouse-server:24.8.6.70 + environment: {CLICKHOUSE_SKIP_USER_SETUP: "1"} + volumes: + - ./out/users.xml:/etc/clickhouse-server/users.xml:ro,z + - ./out:/out:ro,z + ulimits: {nofile: 262144} + mem_limit: 700m +services: + fresh: {<<: *node, ports: ["127.0.0.1:18120:8123"]} + data1: {<<: *node, ports: ["127.0.0.1:18121:8123"]} + data2: {<<: *node, ports: ["127.0.0.1:18122:8123"]} + data3: {<<: *node, ports: ["127.0.0.1:18123:8123"]} diff --git a/ansible/roles/clickhouse/tests/play.yml b/ansible/roles/clickhouse/tests/play.yml new file mode 100644 index 00000000..46b96885 --- /dev/null +++ b/ansible/roles/clickhouse/tests/play.yml @@ -0,0 +1,17 @@ +# The role's SQL-driven tasks, in tasks/main.yml's order, against the test containers +- hosts: all + connection: local + gather_facts: false + vars: + clickhouse_admin_client_config: "{{ lookup('env', 'TESTS') }}/out/admin-{{ inventory_hostname }}.xml" + environment: + CH_NODE: "{{ inventory_hostname }}" + PATH: "{{ lookup('env', 'TESTS') }}/bin:{{ lookup('env', 'PATH') }}" + tasks: + # tasks/main.yml's task, without owner root + - name: Admin client configuration + ansible.builtin.template: {src: ../templates/admin-client.xml.j2, dest: "{{ clickhouse_admin_client_config }}", mode: "0644"} + no_log: true + - {name: quotas, include_role: {name: clickhouse, tasks_from: config/quotas.yml}, when: clickhouse_role_manage_quotas} + - {name: users, include_role: {name: clickhouse, tasks_from: config/users.yml}, when: clickhouse_role_manage_users} + - {name: grants, include_role: {name: clickhouse, tasks_from: config/grants.yml}, when: clickhouse_role_manage_grants} diff --git a/ansible/roles/clickhouse/tests/render.yml b/ansible/roles/clickhouse/tests/render.yml new file mode 100644 index 00000000..db3f8eb6 --- /dev/null +++ b/ansible/roles/clickhouse/tests/render.yml @@ -0,0 +1,7 @@ +# Renders users.xml from the role's template with the test vars, for compose.yml +- hosts: localhost + connection: local + gather_facts: false + vars_files: [../defaults/main.yml, out/vars.yml] + tasks: + - ansible.builtin.template: {src: ../templates/users.xml.j2, dest: out/users.xml, mode: "0644"} diff --git a/ansible/roles/clickhouse/tests/run.sh b/ansible/roles/clickhouse/tests/run.sh new file mode 100755 index 00000000..023d4d8c --- /dev/null +++ b/ansible/roles/clickhouse/tests/run.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Tests the role's SQL-driven users, quotas and grants (test_sql_objects.py) and its apt pin +# (apt_pin.sh). Needs docker compose, ansible-playbook and PyYAML. With rootless podman, +# point DOCKER_HOST at its socket. KEEP=1 leaves the containers running. +set -euo pipefail +cd "$(dirname "$0")" +export COMPOSE=${COMPOSE:-docker compose} +compose() { $COMPOSE -f compose.yml "$@"; } +trap 'rc=$?; [ $rc = 0 ] || compose logs --no-color --tail=50; [ -n "${KEEP:-}" ] || compose down -v --remove-orphans >/dev/null 2>&1' EXIT +compose down -v --remove-orphans >/dev/null 2>&1 # left from a run with KEEP=1 +rm -rf out && mkdir out +python3 test_sql_objects.py render +ANSIBLE_NOCOLOR=1 ansible-playbook -i localhost, render.yml > out/ansible-render.log || { tail -40 out/ansible-render.log; exit 1; } +compose up -d --quiet-pull +python3 test_sql_objects.py +[ -n "${SKIP_APT_PIN:-}" ] || ./apt_pin.sh diff --git a/ansible/roles/clickhouse/tests/seed/data1.sql b/ansible/roles/clickhouse/tests/seed/data1.sql new file mode 100644 index 00000000..83d29ffa --- /dev/null +++ b/ansible/roles/clickhouse/tests/seed/data1.sql @@ -0,0 +1,22 @@ +-- data1 as on 2026-10-26: oonimeasurements created by hand (old limits, no profile, so +-- readonly 2 from the default profile), the writers on the XML write profile, an empty +-- role granted to the user of the same name, the oonimeasurements quota set up and +-- assigned by hand, grants the role doesn't list, and objects that aren't in vars. +-- {password:} is the hash of the user's password from vars. +CREATE USER oonimeasurements IDENTIFIED WITH sha256_hash BY '{password:oonimeasurements}' SETTINGS max_memory_usage = 501001000, max_execution_time = 60; +CREATE USER fastpath IDENTIFIED WITH sha256_hash BY '{password:fastpath}' SETTINGS PROFILE 'write'; +CREATE USER ooniprobe IDENTIFIED WITH sha256_hash BY '{password:ooniprobe}' SETTINGS PROFILE 'write'; +CREATE USER oonirun IDENTIFIED WITH sha256_hash BY '{password:oonirun}' SETTINGS PROFILE 'write'; +CREATE USER oonitestlists IDENTIFIED WITH sha256_hash BY '{password:oonitestlists}' SETTINGS PROFILE 'write'; +CREATE USER notebook IDENTIFIED WITH sha256_hash BY '{password:notebook}' SETTINGS PROFILE 'write'; +CREATE ROLE oonimeasurements; +GRANT oonimeasurements TO oonimeasurements; +GRANT SELECT ON ooni.* TO oonimeasurements, fastpath, ooniprobe, oonirun, notebook; +GRANT INSERT ON ooni.fastpath TO fastpath; +CREATE QUOTA oonimeasurements FOR INTERVAL 600 second MAX queries = 12000, errors = 1000, execution_time = 1000 TO oonimeasurements; +CREATE QUOTA ooniprobe; +GRANT ALTER UPDATE ON ooni.* TO oonimeasurements; +GRANT INSERT ON ooni.fastpath TO oonimeasurements; +GRANT SELECT ON extra.* TO ooniprobe; +CREATE USER manual_user IDENTIFIED WITH plaintext_password BY 'manual'; +CREATE QUOTA manual_quota; diff --git a/ansible/roles/clickhouse/tests/seed/data2.sql b/ansible/roles/clickhouse/tests/seed/data2.sql new file mode 100644 index 00000000..9e91ee41 --- /dev/null +++ b/ansible/roles/clickhouse/tests/seed/data2.sql @@ -0,0 +1,10 @@ +-- data2 as on 2026-10-26: oonimeasurements created by hand with only a memory limit, and +-- quotas that exist but are empty and assigned to nobody. +CREATE USER oonimeasurements IDENTIFIED WITH sha256_hash BY '{password:oonimeasurements}' SETTINGS max_memory_usage = 4294967296; +CREATE USER fastpath IDENTIFIED WITH sha256_hash BY '{password:fastpath}' SETTINGS PROFILE 'write'; +GRANT SELECT ON ooni.* TO oonimeasurements, fastpath; +CREATE QUOTA oonimeasurements; +CREATE QUOTA ooniprobe; +CREATE QUOTA oonirun; +CREATE QUOTA oonitestlists; +CREATE QUOTA notebook; diff --git a/ansible/roles/clickhouse/tests/seed/data3.sql b/ansible/roles/clickhouse/tests/seed/data3.sql new file mode 100644 index 00000000..babecd2f --- /dev/null +++ b/ansible/roles/clickhouse/tests/seed/data3.sql @@ -0,0 +1,9 @@ +-- data3 as on 2026-10-26: the users as upstream's role created them from older vars, with +-- the XML readonly profile (readonly 1, which refuses log_comment) and old passwords. +-- {old_password:} is the hash of a password that isn't the one in vars. +CREATE USER oonimeasurements IDENTIFIED WITH sha256_hash BY '{old_password:oonimeasurements}' SETTINGS max_memory_usage = 1001001000, max_execution_time = 30, max_bytes_to_read = 501001001000, max_rows_to_read = 5001001000, timeout_before_checking_execution_speed = 5, max_result_rows = 51000, PROFILE 'readonly'; +CREATE USER fastpath IDENTIFIED WITH sha256_hash BY '{old_password:fastpath}' SETTINGS PROFILE 'write'; +CREATE USER ooniprobe IDENTIFIED WITH sha256_hash BY '{old_password:ooniprobe}' SETTINGS PROFILE 'write'; +GRANT SELECT ON ooni.* TO oonimeasurements, fastpath, ooniprobe; +GRANT INSERT ON ooni.fastpath TO fastpath; +CREATE QUOTA oonimeasurements; diff --git a/ansible/roles/clickhouse/tests/test_sql_objects.py b/ansible/roles/clickhouse/tests/test_sql_objects.py new file mode 100755 index 00000000..faac59c0 --- /dev/null +++ b/ansible/roles/clickhouse/tests/test_sql_objects.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +""" +Runs the role's SQL-driven tasks (users, quotas, grants) with the real +ansible/group_vars/clickhouse/vars.yml against ClickHouse containers (compose.yml): +`fresh` starts empty, data1-3 are seeded like the production nodes (seed/), and checks +that: + +- every node ends up with the users, privileges and quota limits of an empty node that + only ever had the role, keeping user ids, quota assignments, roles granted by hand and + objects not in vars; +- every user logs in with its password from vars (and not with an old one), can do what + its grants allow and nothing else, and has the settings from vars, CONST ones fixed; +- a second run changes nothing; +- queries running during the role runs never lose their privileges; +- task output never shows a password or its hash, and errors are visible; +- with clickhouse_sql_objects_drop_unlisted, objects not in vars are dropped. + +Called by run.sh; needs ansible-playbook and PyYAML. +""" +import base64, hashlib, json, os, re, subprocess, sys, threading, time, urllib.request + +TESTS = os.path.dirname(os.path.abspath(__file__)) +ROLES = os.path.normpath(os.path.join(TESTS, "../..")) +VARS = os.path.normpath(os.path.join(ROLES, "../group_vars/clickhouse/vars.yml")) +OUT = os.path.join(TESTS, "out") +COMPOSE = os.environ.get("COMPOSE", "docker compose").split() + ["-f", os.path.join(TESTS, "compose.yml")] +NODES = {"fresh": 18120, "data1": 18121, "data2": 18122, "data3": 18123} +SEEDED = ["data1", "data2", "data3"] +failures = [] + + +def check(ok, what): + print(("ok " if ok else "FAIL ") + what) + if not ok: + failures.append(what) + + +# --- test vars: production's vars.yml, with the AWS lookups replaced by test secrets ------- + +def write_test_vars(): + text = open(VARS).read() + text = re.sub(r"lookup\('amazon\.aws\.aws_(?:ssm|secret)', '([^']+)'[^)]*\)", + lambda m: "'test-" + m.group(1).rsplit("/", 1)[-1] + "'", text) + os.makedirs(OUT, exist_ok=True) + open(os.path.join(OUT, "vars.yml"), "w").write(text) + import yaml + v = yaml.safe_load(text) + # each user's password is "{{ 'test-...' | hash('sha256') }}" + passwords = {u["name"]: re.search(r"'([^']+)' \| hash\('sha256'\)", u["password"]).group(1) + for u in v["clickhouse_custom_users"]} + admin = re.fullmatch(r"\{\{ '([^']+)' \}\}", v["clickhouse_admin_password"]).group(1) + return v, passwords, admin + + +def sha256(s): + return hashlib.sha256(s.encode()).hexdigest() + + +# --- ClickHouse access --------------------------------------------------------------- + +def admin_sql(node, sql): + """As admin, inside the node's container (admin may only connect from localhost).""" + r = subprocess.run(COMPOSE + ["exec", "-T", node, "clickhouse-client", "-u", ADMIN_USER, + "--password", ADMIN_PASSWORD, "--multiquery"], + input=sql, capture_output=True, text=True) + if r.returncode: + raise RuntimeError(f"{node}: {r.stderr.strip()}") + return r.stdout + + +def user_sql(node, user, password, sql): + """As a service user, over HTTP. Returns (ok, text): text is the error on failure.""" + req = urllib.request.Request(f"http://127.0.0.1:{NODES[node]}/", data=sql.encode(), headers={ + "X-ClickHouse-User": user, "X-ClickHouse-Key": password}) + try: + with urllib.request.urlopen(req, timeout=30) as r: + return True, r.read().decode().strip() + except urllib.error.HTTPError as e: + return False, e.read().decode().strip() + except OSError as e: + return False, str(e) + + +def error_code(text): + m = re.search(r"Code: (\d+)", text) + return int(m.group(1)) if m else None + + +def state(node): + """What the role manages on a node: users, grants, quotas and SQL objects not in vars.""" + s = {"ids": {}, "users": {}, "quotas": {}} + for line in admin_sql(node, "SELECT name, id FROM system.users WHERE storage = 'local_directory' FORMAT TSV").splitlines(): + name, uid = line.split("\t") + s["ids"][name] = uid + for u in V["clickhouse_custom_users"]: + if u["name"] in s["ids"]: + grants = admin_sql(node, f"SHOW GRANTS FOR {u['name']}").splitlines() + # privileges ("GRANT ... ON ..."), which the role manages, apart from granted roles + s["users"][u["name"]] = (admin_sql(node, f"SHOW CREATE USER {u['name']}").strip(), + [g for g in grants if " ON " in g]) + s.setdefault("roles", {})[u["name"]] = [g for g in grants if " ON " not in g] + names = admin_sql(node, "SELECT name FROM system.quotas WHERE storage = 'local_directory' FORMAT TSV").split() + for q in names: + s["quotas"][q] = admin_sql(node, f"SHOW CREATE QUOTA {q}").strip() + return s + + +def quota_limits(show_create): + return re.sub(r" TO .*$", "", show_create) + + +def quota_to(show_create): + m = re.search(r" TO (.*)$", show_create) + return m.group(1) if m else None + + +# --- running the role ---------------------------------------------------------------- + +def run_role(label, limit=None, extra=None): + inventory = os.path.join(OUT, "inventory.ini") + open(inventory, "w").write("\n".join(NODES) + "\n") + cmd = ["ansible-playbook", "-i", inventory, os.path.join(TESTS, "play.yml"), + "-e", "@" + os.path.join(OUT, "vars.yml")] + if limit: + cmd += ["--limit", limit] + if extra: + cmd += ["-e", json.dumps(extra)] + env = dict(os.environ, ANSIBLE_ROLES_PATH=ROLES, TESTS=TESTS, ANSIBLE_NOCOLOR="1", + ANSIBLE_PYTHON_INTERPRETER=sys.executable, COMPOSE=" ".join(COMPOSE[:-2])) + r = subprocess.run(cmd, env=env, capture_output=True, text=True) + log = r.stdout + r.stderr + open(os.path.join(OUT, f"ansible-{label}.log"), "w").write(log) + if r.returncode and label != "error": # the "error" run is meant to fail + print(log[-3000:]) + return r.returncode, log + + +def leaks(log): + """Passwords or password hashes from the test vars that appear in ansible's output.""" + secrets = [ADMIN_PASSWORD, sha256(ADMIN_PASSWORD)] + for p in PASSWORDS.values(): + secrets += [p, sha256(p)] + return [s[:12] + "..." for s in secrets if s in log] + + +class Probe(threading.Thread): + """Inserts as fastpath and selects as oonimeasurements on a node until stopped.""" + def __init__(self, node): + super().__init__(daemon=True) + self.node, self.ok, self.errors, self.stop = node, 0, [], threading.Event() + + def run(self): + while not self.stop.is_set(): + for user, sql in (("fastpath", "INSERT INTO ooni.fastpath VALUES (1)"), + ("oonimeasurements", "SELECT count() FROM ooni.probe")): + ok, text = user_sql(self.node, user, PASSWORDS[user], sql) + if ok: + self.ok += 1 + else: + self.errors.append(f"{user}: {text[:120]}") + + +# --- per-user checks derived from vars --------------------------------------------------- + +def granted(user, privilege, database, table): + return any(user in g["to"] and privilege in g["privileges"] and database in g["databases"] + and (table in g["tables"] or "*" in g["tables"]) for g in V["clickhouse_custom_grants"]) + + +def grant_universe(): + """Every database.table the grants name, with "*" as the table `probe`, and extra.t, + which no one is granted, so that a too broad grant shows.""" + tables = {("extra", "t")} + for g in V["clickhouse_custom_grants"]: + for d in g["databases"]: + for t in g["tables"]: + tables.add((d, "probe" if t == "*" else t)) + return sorted(tables) + + +def check_user(node, user): + name, password = user["name"], PASSWORDS[user["name"]] + ok, text = user_sql(node, name, password, "SELECT currentUser()") + check(ok and text == name, f"{node}: {name} logs in with its password from vars") + for database, table in grant_universe(): + for privilege, sql in (("SELECT", f"SELECT count() FROM {database}.{table}"), + ("INSERT", f"INSERT INTO {database}.{table} VALUES (1)")): + ok, text = user_sql(node, name, password, sql) + want = granted(name, privilege, database, table) + check(ok == want and (ok or error_code(text) == 497), + f"{node}: {name} {'can' if want else 'cannot'} {privilege} {database}.{table}") + for setting in user.get("settings", []): + key, value, const = re.fullmatch(r"(\w+) = (\S+)( CONST)?", setting).groups() + ok, text = user_sql(node, name, password, f"SELECT getSetting('{key}')") + check(ok and float(text) == float(value), f"{node}: {name} has {key} = {value}") + if const: + ok, text = user_sql(node, name, password, f"SELECT 1 SETTINGS {key} = {int(float(value)) * 2}") + check(not ok and error_code(text) == 452, f"{node}: {name} cannot raise {key}") + ok, text = user_sql(node, name, password, "SELECT 1 SETTINGS log_comment = 'test'") + check(ok, f"{node}: {name} can set log_comment") + + +# --- the test ------------------------------------------------------------------------ + +def setup(): + tables = grant_universe() + ddl = "".join(f"CREATE DATABASE IF NOT EXISTS {d};\n" for d in sorted({d for d, _ in tables} | {"extra"})) + ddl += "".join(f"CREATE TABLE IF NOT EXISTS {d}.{t} (x UInt8) ENGINE = Memory;\n" for d, t in tables) + for node in NODES: + for _ in range(60): + try: + admin_sql(node, "SELECT 1") + break + except RuntimeError: + time.sleep(1) + admin_sql(node, ddl) + for node in SEEDED: + sql = open(os.path.join(TESTS, "seed", f"{node}.sql")).read() + sql = re.sub(r"\{password:(\w+)\}", lambda m: sha256(PASSWORDS[m.group(1)]), sql) + sql = re.sub(r"\{old_password:(\w+)\}", lambda m: sha256("old-" + m.group(1)), sql) + admin_sql(node, sql) + + +def main(): + global V, PASSWORDS, ADMIN_USER, ADMIN_PASSWORD + V, PASSWORDS, ADMIN_PASSWORD = write_test_vars() + ADMIN_USER = V["clickhouse_admin_user"] + if sys.argv[1:] == ["render"]: # run.sh renders users.xml before starting the containers + return + setup() + before = {n: state(n) for n in NODES} + check(user_sql("data3", "oonimeasurements", PASSWORDS["oonimeasurements"], "SELECT 1")[0] is False, + "data3: before the role, oonimeasurements has an old password") + + probe = Probe("data1") + probe.start() + rc1, log1 = run_role("run1") + rc2, log2 = run_role("run2") + probe.stop.set() + probe.join() + check(rc1 == 0, "first role run succeeds") + check(rc2 == 0, "second role run succeeds") + check(not probe.errors and probe.ok > 20, + f"data1: {probe.ok} queries during the runs, failures: {probe.errors[:3]}") + check(not leaks(log1 + log2), f"no password or hash in the output: {leaks(log1 + log2)}") + # What the probe can't catch reliably, a gap between two statements, can't happen if + # the role never revokes a privilege a user keeps: all users in vars have grants, so it + # sends no REVOKE at all. + for n in NODES: + admin_sql(n, "SYSTEM FLUSH LOGS") + revokes = admin_sql(n, "SELECT count() FROM system.query_log WHERE type = 'QueryStart' " + f"AND user = '{ADMIN_USER}' AND query ILIKE '%REVOKE%'").strip() + check(revokes == "0", f"{n}: the role sent no REVOKE ({revokes})") + + after = {n: state(n) for n in NODES} + for node in NODES: + users = after[node]["users"] + check(set(users) == {u["name"] for u in V["clickhouse_custom_users"]}, f"{node}: every user in vars exists") + if node != "fresh": + check(users == after["fresh"]["users"], f"{node}: users and grants are those of a fresh node") + kept = {n: i for n, i in before[node]["ids"].items() if n in after[node]["ids"]} + check(all(after[node]["ids"][n] == i for n, i in kept.items()), f"{node}: user ids are kept") + check(set(before[node]["ids"]) <= set(after[node]["ids"]), f"{node}: users not in vars are kept") + check(set(before[node]["quotas"]) <= set(after[node]["quotas"]), f"{node}: quotas not in vars are kept") + check(after[node].get("roles") == {u: before[node].get("roles", {}).get(u, []) for u in after[node]["roles"]}, + f"{node}: roles granted by hand are kept") + for q in V["clickhouse_custom_quotas"]: + show = after[node]["quotas"].get(q["name"], "") + check(quota_limits(show) == quota_limits(after["fresh"]["quotas"][q["name"]]), + f"{node}: quota {q['name']} has the limits from vars") + want_to = ", ".join(q["to"]) if "to" in q else quota_to(before[node]["quotas"].get(q["name"], "")) + check(quota_to(show) == want_to, f"{node}: quota {q['name']} is assigned to {want_to or 'nobody'}") + for user in V["clickhouse_custom_users"]: + check_user(node, user) + check(user_sql("data3", "oonimeasurements", "old-oonimeasurements", "SELECT 1")[0] is False, + "data3: the old password no longer works") + + rc3, _ = run_role("run3") + check(rc3 == 0 and {n: state(n) for n in NODES} == after, "a third run changes nothing") + + # An invalid setting fails the run, with ClickHouse's error and without the password hash + bad = [dict(u, settings=u.get("settings", []) + ["not_a_setting = 1"]) if u["name"] == "oonimeasurements" else u + for u in V["clickhouse_custom_users"]] + rc, log = run_role("error", limit="data1", extra={"clickhouse_custom_users": bad}) + check(rc != 0 and "UNKNOWN_SETTING" in log, "an invalid setting fails with ClickHouse's error in the output") + check(not leaks(log), f"no password or hash in the error output: {leaks(log)}") + + rc, _ = run_role("drop", limit="data1", extra={"clickhouse_sql_objects_drop_unlisted": True}) + s = state("data1") + check(rc == 0 and "manual_user" not in s["ids"] and "manual_quota" not in s["quotas"], + "data1: with clickhouse_sql_objects_drop_unlisted, users and quotas not in vars are dropped") + check(s["users"] == after["fresh"]["users"], "data1: users in vars are unaffected by the drop") + + print(f"\n{len(failures)} failed" if failures else "\nall passed") + sys.exit(1 if failures else 0) + + +if __name__ == "__main__": + main() From 8e2f8ec8d27f1fcbbfb325758e0bbdff7cd46050 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 08/16] oonidata_clickhouse: create the ooni database on a new replica from schema.sql On a cluster member (a host with a replica macro in clickhouse_macros) that has no ooni database, a new replica or a re-imaged one, the role creates the database and loads scripts/cluster-migration/schema.sql on that host only, with its ON CLUSTER removed. The replicated tables then fetch their data from the other replicas; the tables that aren't replicated start empty. A host that has the database is left alone: changes to it are migrations. Hosts that aren't replicas, like notebook1, are skipped. If loading schema.sql fails, the deploy stops with ClickHouse's error and the database is left as it is, for an operator to repair; nothing is dropped. The runbook says how to add and remove a replica: edit the cluster in vars, deploy one host at a time, and drop a removed replica from Keeper; how to finish a failed schema load by hand; and, as replicated tables need a Keeper quorum, to deploy a new cluster with --skip-tags clickhouse_schema first. --- .../oonidata_clickhouse/defaults/main.yml | 8 ++++ .../roles/oonidata_clickhouse/tasks/main.yml | 7 +++ .../oonidata_clickhouse/tasks/schema.yml | 42 ++++++++++++++++++ docs/Runbooks.md | 44 +++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 ansible/roles/oonidata_clickhouse/tasks/schema.yml diff --git a/ansible/roles/oonidata_clickhouse/defaults/main.yml b/ansible/roles/oonidata_clickhouse/defaults/main.yml index e69de29b..17e7032f 100644 --- a/ansible/roles/oonidata_clickhouse/defaults/main.yml +++ b/ansible/roles/oonidata_clickhouse/defaults/main.yml @@ -0,0 +1,8 @@ +--- +# The ooni database's schema (tasks/schema.yml), loaded on cluster members that don't +# have the database yet +oonidata_clickhouse_database: ooni +oonidata_clickhouse_schema: "{{ playbook_dir }}/../scripts/cluster-migration/schema.sql" +# Cluster members: the hosts named in a clickhouse_macros entry's `server` (each has its +# replica macro there) +oonidata_clickhouse_replica: "{{ inventory_hostname in (clickhouse_macros | default([]) | selectattr('server', 'defined') | map(attribute='server') | flatten) }}" diff --git a/ansible/roles/oonidata_clickhouse/tasks/main.yml b/ansible/roles/oonidata_clickhouse/tasks/main.yml index 75103fba..308c4a76 100644 --- a/ansible/roles/oonidata_clickhouse/tasks/main.yml +++ b/ansible/roles/oonidata_clickhouse/tasks/main.yml @@ -4,6 +4,13 @@ - oonidata - clickhouse +- name: Create the ooni database on a new replica + ansible.builtin.include_tasks: schema.yml + tags: + - oonidata + - clickhouse + - clickhouse_schema + - name: Create nftables rule for clickhouse native port set_fact: ch_nft_rule: '{{ ch_nft_rule | default([]) + [''add rule inet filter input ip saddr '' + item.ip + '' tcp dport 9000 counter accept comment "incoming clickhouse from '' + item.fqdn + ''"''] }}' diff --git a/ansible/roles/oonidata_clickhouse/tasks/schema.yml b/ansible/roles/oonidata_clickhouse/tasks/schema.yml new file mode 100644 index 00000000..04301058 --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tasks/schema.yml @@ -0,0 +1,42 @@ +--- + +# A cluster member without the ooni database gets it from the repo's schema.sql: a new +# replica, or one that was re-imaged. It is created on this host only (the file's +# ON CLUSTER is removed), and the replicated tables then fetch their data from the other +# replicas; the few tables that aren't replicated start empty. A host that has the +# database is left alone: changes to it go in scripts/cluster-migration/migrations/. +# A re-imaged replica must first be dropped from Keeper (SYSTEM DROP REPLICA), or +# creating its replicated tables fails because the replica already exists. +# If loading schema.sql fails, the deploy stops with ClickHouse's error and leaves the +# database as it is; once the cause is fixed, load schema.sql by hand on that host (see +# docs/Runbooks.md, "Adding and removing a replica"): its statements are IF NOT EXISTS. + +- name: Create the ooni database on a cluster member that doesn't have it + when: oonidata_clickhouse_replica + tags: [clickhouse_schema] + block: + - name: Check whether the ooni database exists + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --query "EXISTS DATABASE {{ oonidata_clickhouse_database }}" + register: oonidata_clickhouse_database_exists + changed_when: false + check_mode: false # read-only + + - name: Create the ooni database from schema.sql + when: oonidata_clickhouse_database_exists.stdout == "0" + block: + - name: Create the ooni database + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --query "CREATE DATABASE {{ oonidata_clickhouse_database }}" + + # schema.sql's statements say ON CLUSTER oonidata_cluster; run them on this host only + - name: Create the tables and views of schema.sql + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --database {{ oonidata_clickhouse_database }} --multiquery + stdin: "{{ lookup('file', oonidata_clickhouse_schema) | regex_replace(' ON CLUSTER \\w+', '') }}" diff --git a/docs/Runbooks.md b/docs/Runbooks.md index 1a7ece6b..88a52eb8 100644 --- a/docs/Runbooks.md +++ b/docs/Runbooks.md @@ -1185,6 +1185,50 @@ PARTITION BY concat(substring(bucket_date, 1, 4), substring(bucket_date, 6, 2)) PRIMARY KEY (measurement_uid, observation_idx) ORDER BY (measurement_uid, observation_idx, measurement_start_time, hostname) SETTINGS index_granularity = 8192 ``` +### Adding and removing a replica + +The cluster's replicas are defined in `ansible/group_vars/clickhouse/vars.yml`: +`clickhouse_remote_servers` (the replicas of `oonidata_cluster`) and `clickhouse_macros` (each +host's `shard` and `replica`). `ansible/roles/oonidata_clickhouse/tests/run.sh` does all of this +on containers. + +To add a replica, e.g. `data4.htz-fsn.prod.ooni.nu` as `clickhouse4.prod.ooni.io`: + +1. Add the host to the inventory's `[clickhouse]` group and to `deploy-clickhouse.yml`'s hosts, + a replica to `clickhouse_remote_servers`, and a macro with an unused replica name + (`04`) to `clickhouse_macros`. It doesn't join Keeper unless it's also + added to `clickhouse_keeper` and `clickhouse_zookeeper`. +2. Deploy the existing replicas one at a time, then the new one: + ``` + ./play deploy-clickhouse.yml -l data1.htz-fsn.prod.ooni.nu --diff -i inventory + ``` + On the new host, `oonidata_clickhouse` creates the `ooni` database from + `scripts/cluster-migration/schema.sql`, and its replicated tables fetch their data from the + other replicas. Tables that aren't replicated start empty. Only migrations change the + database of a replica that has it. If loading `schema.sql` fails, the deploy stops with + ClickHouse's error and leaves the database as it is: once the cause is fixed, load it by + hand on that host (its statements are `IF NOT EXISTS`, so it only creates what's missing): + ``` + sed 's/ ON CLUSTER [a-z_]*//' scripts/cluster-migration/schema.sql | ssh data4.htz-fsn.prod.ooni.nu \ + sudo clickhouse-client --config-file /etc/clickhouse-client/ansible-admin.xml --database ooni --multiquery + ``` + +A new cluster has no Keeper quorum until two of its three hosts run, so deploy its hosts +with `--skip-tags clickhouse_schema` first, then once more with `--tags clickhouse_schema` to +create the `ooni` database on each. + +To remove a replica: + +1. Remove it from `clickhouse_remote_servers` and `clickhouse_macros`, and deploy the remaining + replicas one at a time. +2. Stop ClickHouse on the removed host, then remove its replica from Keeper, on any other replica: + ```sql + SYSTEM DROP REPLICA '04' + ``` + +A re-imaged replica that keeps its name must also be dropped from Keeper before it's deployed +again, or creating its replicated tables fails because the replica already exists. + ### Adding users to notebook server In order to add new users to the jupyterlab notebook server hosted From 1189361c82165ec39f0bb01784410abfdae4dea1 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:22 +0000 Subject: [PATCH 09/16] oonidata_clickhouse: test adding and removing a replica tests/run.sh, also run in CI, follows the runbook on one ClickHouse 24.8.6.70 container per production host, named like it, with the real group and host vars (secrets replaced by test values) and the configs the clickhouse role renders from them: - data1-3 form the Keeper ensemble and oonidata_cluster, and the schema step creates the ooni database on each, but not on notebook1, and leaves existing databases alone; - with data4 added to clickhouse_remote_servers and clickhouse_macros and data1-3 restarted one at a time, every node sees four replicas, data4 gets the database and catches up, and inserts and ON CLUSTER DDL reach it; - with data4 removed and dropped from Keeper, every node sees three replicas, Keeper has no trace of it, ON CLUSTER DDL doesn't wait for it, and data1-3 still replicate; - a re-imaged data4 (empty disk, same replica name) can be added again. --- .github/workflows/test_clickhouse_role.yml | 25 +- .../oonidata_clickhouse/tests/.gitignore | 1 + .../roles/oonidata_clickhouse/tests/README.md | 22 ++ .../tests/bin/clickhouse-client | 7 + .../oonidata_clickhouse/tests/compose.yml | 33 +++ .../oonidata_clickhouse/tests/render.yml | 15 + .../roles/oonidata_clickhouse/tests/run.sh | 12 + .../oonidata_clickhouse/tests/schema.yml | 16 + .../tests/test_replicas.py | 280 ++++++++++++++++++ 9 files changed, 407 insertions(+), 4 deletions(-) create mode 100644 ansible/roles/oonidata_clickhouse/tests/.gitignore create mode 100644 ansible/roles/oonidata_clickhouse/tests/README.md create mode 100755 ansible/roles/oonidata_clickhouse/tests/bin/clickhouse-client create mode 100644 ansible/roles/oonidata_clickhouse/tests/compose.yml create mode 100644 ansible/roles/oonidata_clickhouse/tests/render.yml create mode 100755 ansible/roles/oonidata_clickhouse/tests/run.sh create mode 100644 ansible/roles/oonidata_clickhouse/tests/schema.yml create mode 100755 ansible/roles/oonidata_clickhouse/tests/test_replicas.py diff --git a/.github/workflows/test_clickhouse_role.yml b/.github/workflows/test_clickhouse_role.yml index 203ce3ce..26a57e5a 100644 --- a/.github/workflows/test_clickhouse_role.yml +++ b/.github/workflows/test_clickhouse_role.yml @@ -1,17 +1,24 @@ # Tests the clickhouse role's SQL users, quotas and grants and its apt pin -# (ansible/roles/clickhouse/tests) when the role or its vars change -name: Test ClickHouse role +# (ansible/roles/clickhouse/tests), and adding and removing a replica with oonidata_clickhouse +# (ansible/roles/oonidata_clickhouse/tests), when the roles, their vars or the schema change +name: Test ClickHouse roles on: pull_request: paths: - "ansible/roles/clickhouse/**" - - "ansible/group_vars/clickhouse/vars.yml" + - "ansible/roles/oonidata_clickhouse/**" + - "ansible/group_vars/**" + - "ansible/host_vars/data*" + - "scripts/cluster-migration/schema.sql" - ".github/workflows/test_clickhouse_role.yml" push: branches: [main] paths: - "ansible/roles/clickhouse/**" - - "ansible/group_vars/clickhouse/vars.yml" + - "ansible/roles/oonidata_clickhouse/**" + - "ansible/group_vars/**" + - "ansible/host_vars/data*" + - "scripts/cluster-migration/schema.sql" - ".github/workflows/test_clickhouse_role.yml" jobs: @@ -25,3 +32,13 @@ jobs: - if: failure() uses: actions/upload-artifact@v4 with: {name: sql-objects-out, path: ansible/roles/clickhouse/tests/out/} + replicas: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - run: pipx install ansible-core==2.17.7 && sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends python3-yaml + - run: ansible/roles/oonidata_clickhouse/tests/run.sh + - if: failure() + uses: actions/upload-artifact@v4 + with: {name: replicas-out, path: ansible/roles/oonidata_clickhouse/tests/out/} diff --git a/ansible/roles/oonidata_clickhouse/tests/.gitignore b/ansible/roles/oonidata_clickhouse/tests/.gitignore new file mode 100644 index 00000000..89f9ac04 --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/ansible/roles/oonidata_clickhouse/tests/README.md b/ansible/roles/oonidata_clickhouse/tests/README.md new file mode 100644 index 00000000..ca8bfb9f --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/README.md @@ -0,0 +1,22 @@ +# Tests + +`run.sh` (`test_replicas.py`) adds a replica to the cluster and removes it again the way the +runbook says (docs/Runbooks.md, "Adding and removing a replica"): by editing the cluster in +`ansible/group_vars/clickhouse/vars.yml` and rerunning the roles. It uses the real group and +host vars (secrets replaced by test values) and the configs the clickhouse role renders from +them, with one ClickHouse 24.8.6.70 container per production host, named and aliased like it +(`compose.yml`). It checks that: + +- data1-3 form the Keeper ensemble and `oonidata_cluster`, and `oonidata_clickhouse` creates + the `ooni` database from `scripts/cluster-migration/schema.sql` on each, but not on notebook1 + (in `[clickhouse]`, not a replica), and leaves existing databases alone; +- with data4 added to `clickhouse_remote_servers` and `clickhouse_macros`, and data1-3 + restarted one at a time, every node sees four replicas, data4 gets the database and catches + up, and inserts and `ON CLUSTER` DDL reach it; +- with data4 removed and dropped from Keeper, every node sees three replicas, Keeper has no + trace of it, `ON CLUSTER` DDL doesn't wait for it, and data1-3 still replicate; +- a re-imaged data4 (empty disk, same replica name) can be added again. + +Needs docker compose (or rootless podman with +`DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock`), ansible-playbook and PyYAML. +`KEEP=1` leaves the containers running. The ansible output of each run is in `out/`. diff --git a/ansible/roles/oonidata_clickhouse/tests/bin/clickhouse-client b/ansible/roles/oonidata_clickhouse/tests/bin/clickhouse-client new file mode 100755 index 00000000..66ad5f7b --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/bin/clickhouse-client @@ -0,0 +1,7 @@ +#!/bin/bash +# The roles run clickhouse-client on the ClickHouse host; run it in that host's container +# instead (CH_NODE), where tests/out/ is mounted at /out. +set -euo pipefail +args=() +for a in "$@"; do args+=("${a/#$TESTS\/out\//\/out\/}"); done +exec $COMPOSE -f "$TESTS/compose.yml" exec -T "$CH_NODE" clickhouse-client "${args[@]}" diff --git a/ansible/roles/oonidata_clickhouse/tests/compose.yml b/ansible/roles/oonidata_clickhouse/tests/compose.yml new file mode 100644 index 00000000..0ed6cd3f --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/compose.yml @@ -0,0 +1,33 @@ +# ClickHouse at production's version, one container per host, with the production host +# names as hostname and alias, so that the configs the clickhouse role renders from the +# real vars (out//) work unchanged: Keeper and replication use clickhouseN.prod.ooni.io, +# the role matches hosts by their inventory name dataN.htz-fsn.prod.ooni.nu. +# data4 is the replica that the test adds and removes. +x-node: &node + image: docker.io/clickhouse/clickhouse-server:24.8.6.70 + ulimits: {nofile: 262144} + mem_limit: 1500m +services: + data1: + <<: *node + hostname: clickhouse1.prod.ooni.io + networks: {default: {aliases: [data1.htz-fsn.prod.ooni.nu, clickhouse1.prod.ooni.io]}} + volumes: ["./out/data1.htz-fsn.prod.ooni.nu:/etc/clickhouse-server:ro,z", "./out:/out:ro,z"] + data2: + <<: *node + hostname: clickhouse2.prod.ooni.io + networks: {default: {aliases: [data2.htz-fsn.prod.ooni.nu, clickhouse2.prod.ooni.io]}} + volumes: ["./out/data2.htz-fsn.prod.ooni.nu:/etc/clickhouse-server:ro,z", "./out:/out:ro,z"] + data3: + <<: *node + hostname: clickhouse3.prod.ooni.io + networks: {default: {aliases: [data3.htz-fsn.prod.ooni.nu, clickhouse3.prod.ooni.io]}} + # data3's clickhouse_base_path is /data/clickhouse + volumes: ["./out/data3.htz-fsn.prod.ooni.nu:/etc/clickhouse-server:ro,z", "./out:/out:ro,z", "data3:/data"] + data4: + <<: *node + hostname: clickhouse4.prod.ooni.io + networks: {default: {aliases: [data4.htz-fsn.prod.ooni.nu, clickhouse4.prod.ooni.io]}} + volumes: ["./out/data4.htz-fsn.prod.ooni.nu:/etc/clickhouse-server:ro,z", "./out:/out:ro,z"] +volumes: + data3: diff --git a/ansible/roles/oonidata_clickhouse/tests/render.yml b/ansible/roles/oonidata_clickhouse/tests/render.yml new file mode 100644 index 00000000..6b3e29a8 --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/render.yml @@ -0,0 +1,15 @@ +# Renders each host's config.xml and users.xml with the clickhouse role's own tasks, into +# out//, which compose.yml mounts as the container's /etc/clickhouse-server. +# The test restarts the containers itself, one at a time, as serial: 1 would. notebook1 +# is rendered too, as it's in [clickhouse], but has no container. +- hosts: clickhouse + connection: local + gather_facts: false + vars: + clickhouse_config_directory: "{{ lookup('env', 'TESTS') }}/out/{{ inventory_hostname }}" + clickhouse_handler_on_config_change: Config changed + handlers: + - {name: Config changed, ansible.builtin.debug: {msg: "the test restarts the container"}} + tasks: + - ansible.builtin.file: {path: "{{ clickhouse_config_directory }}", state: directory, mode: "0755"} + - ansible.builtin.include_role: {name: clickhouse, tasks_from: config/clickhouse.yml} diff --git a/ansible/roles/oonidata_clickhouse/tests/run.sh b/ansible/roles/oonidata_clickhouse/tests/run.sh new file mode 100755 index 00000000..38d4c454 --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Adds a replica to the cluster and removes it again through vars and the roles +# (test_replicas.py). Needs docker compose, ansible-playbook and PyYAML. With rootless +# podman, point DOCKER_HOST at its socket. KEEP=1 leaves the containers running. +set -euo pipefail +cd "$(dirname "$0")" +export COMPOSE=${COMPOSE:-docker compose} +compose() { $COMPOSE -f compose.yml "$@"; } +trap 'rc=$?; [ $rc = 0 ] || compose logs --no-color --tail=40; [ -n "${KEEP:-}" ] || compose down -v --remove-orphans >/dev/null 2>&1' EXIT +compose down -v --remove-orphans >/dev/null 2>&1 # left from a run with KEEP=1 +rm -rf out && mkdir out +python3 test_replicas.py diff --git a/ansible/roles/oonidata_clickhouse/tests/schema.yml b/ansible/roles/oonidata_clickhouse/tests/schema.yml new file mode 100644 index 00000000..e890dae1 --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/schema.yml @@ -0,0 +1,16 @@ +# oonidata_clickhouse's schema step (tasks/schema.yml) against the test containers +- hosts: all + connection: local + gather_facts: false + vars: + clickhouse_admin_client_config: "{{ lookup('env', 'TESTS') }}/out/admin-{{ inventory_hostname }}.xml" + oonidata_clickhouse_schema: "{{ lookup('env', 'SCHEMA') }}" + environment: + CH_NODE: "{{ inventory_hostname.split('.')[0] }}" + PATH: "{{ lookup('env', 'TESTS') }}/bin:{{ lookup('env', 'PATH') }}" + tasks: + # the clickhouse role's admin client config task, without owner root + - name: Admin client configuration + ansible.builtin.template: {src: ../../clickhouse/templates/admin-client.xml.j2, dest: "{{ clickhouse_admin_client_config }}", mode: "0644"} + no_log: true + - ansible.builtin.include_role: {name: oonidata_clickhouse, tasks_from: schema.yml} diff --git a/ansible/roles/oonidata_clickhouse/tests/test_replicas.py b/ansible/roles/oonidata_clickhouse/tests/test_replicas.py new file mode 100755 index 00000000..ca00b8da --- /dev/null +++ b/ansible/roles/oonidata_clickhouse/tests/test_replicas.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +""" +Adds a replica to the cluster and removes it again, the way an operator would: by editing +the cluster in vars and rerunning the roles. Uses the real ansible/group_vars and host_vars +(secrets replaced by test values) and the clickhouse role's rendered configs, with one +container per production host (compose.yml), and checks that: + +- data1-3 form the Keeper ensemble and oonidata_cluster, and oonidata_clickhouse's schema + step creates the ooni database from schema.sql on each, but not on a non-member host; +- after data4 is added to clickhouse_remote_servers and clickhouse_macros, every node + sees four replicas, the schema step creates the database on data4 alone, data4's + replicated tables catch up, and ON CLUSTER DDL and inserts reach it; +- after data4 is removed from vars and from Keeper (SYSTEM DROP REPLICA), every node sees + three replicas, Keeper has no trace of it, ON CLUSTER DDL doesn't wait for it, and + data1-3 still replicate; +- a re-imaged data4 (empty disk, same replica name) can be added again. + +Called by run.sh; needs ansible-playbook and PyYAML. +""" +import os, re, shutil, subprocess, sys, time +import yaml + +TESTS = os.path.dirname(os.path.abspath(__file__)) +ANSIBLE = os.path.normpath(os.path.join(TESTS, "../../..")) +REPO = os.path.dirname(ANSIBLE) +SCHEMA = os.path.join(REPO, "scripts/cluster-migration/schema.sql") +OUT = os.path.join(TESTS, "out") +INV = os.path.join(OUT, "inventory") +COMPOSE = os.environ.get("COMPOSE", "docker compose").split() + ["-f", os.path.join(TESTS, "compose.yml")] +HOSTS = {f"data{i}": f"data{i}.htz-fsn.prod.ooni.nu" for i in (1, 2, 3, 4)} +NON_MEMBER = "notebook1.htz-fsn.prod.ooni.nu" # in [clickhouse], as in production, but not a replica +failures = [] + + +def check(ok, what): + print(("ok " if ok else "FAIL ") + what) + if not ok: + failures.append(what) + + +def secrets_replaced(text): + return re.sub(r"lookup\('amazon\.aws\.aws_(?:ssm|secret)', '([^']+)'[^)]*\)", + lambda m: "'test-" + m.group(1).rsplit("/", 1)[-1] + "'", text) + + +# --- the inventory: production's vars, and a cluster that the test edits ----------------- + +def write_inventory(): + shutil.rmtree(INV, ignore_errors=True) + os.makedirs(os.path.join(INV, "group_vars")) + os.makedirs(os.path.join(INV, "host_vars")) + for src, dst in (("group_vars/all/vars.yml", "group_vars/all.yml"), + ("group_vars/clickhouse/vars.yml", "group_vars/clickhouse.yml")): + open(os.path.join(INV, dst), "w").write(secrets_replaced(open(os.path.join(ANSIBLE, src)).read())) + for host in list(HOSTS.values()) + [NON_MEMBER]: + src = os.path.join(ANSIBLE, "host_vars", host) + if os.path.isfile(src): + open(os.path.join(INV, "host_vars", host + ".yml"), "w").write(secrets_replaced(open(src).read())) + return yaml.safe_load(open(os.path.join(INV, "group_vars/clickhouse.yml"))) + + +def set_cluster(nodes): + """The cluster in vars as an operator edits it: a replica in oonidata_cluster and a + replica macro for each node (data1-3 keep production's entries).""" + v = dict(VARS) + replicas = [{"replica": None, "host": f"clickhouse{n[-1]}.prod.ooni.io", "port": 9000} for n in nodes] + server = dict(v["clickhouse_remote_servers"][0]) + server["shards"] = [dict(server["shards"][0], replicas=replicas)] + v["clickhouse_remote_servers"] = [server] + macros = [m for m in VARS["clickhouse_macros"] if "server" not in m or m["server"][0] in [HOSTS[n] for n in nodes]] + if "data4" in nodes: + macros.insert(-1, {"macro": "01\n04\n", "server": [HOSTS["data4"]]}) + v["clickhouse_macros"] = macros + yaml.safe_dump(v, open(os.path.join(INV, "group_vars/clickhouse.yml"), "w"), sort_keys=False) + open(os.path.join(INV, "hosts.ini"), "w").write( + "[clickhouse]\n" + "\n".join([HOSTS[n] for n in nodes] + [NON_MEMBER]) + "\n") + + +def ansible(playbook, limit=None): + cmd = ["ansible-playbook", "-i", os.path.join(INV, "hosts.ini"), os.path.join(TESTS, playbook)] + if limit: + cmd += ["--limit", limit] + env = dict(os.environ, ANSIBLE_ROLES_PATH=os.path.join(ANSIBLE, "roles"), TESTS=TESTS, SCHEMA=SCHEMA, + ANSIBLE_NOCOLOR="1", ANSIBLE_PYTHON_INTERPRETER=sys.executable, + COMPOSE=" ".join(COMPOSE[:-2])) + r = subprocess.run(cmd, env=env, capture_output=True, text=True) + open(os.path.join(OUT, f"ansible-{playbook}-{time.strftime('%H%M%S')}.log"), "w").write(r.stdout + r.stderr) + if r.returncode: + print(r.stdout[-3000:] + r.stderr[-2000:]) + return r.returncode, r.stdout + + +def status(stdout, task, host): + """What `task` reported for `host` in ansible's output: ok, changed, skipping or None.""" + m = re.search(r"TASK \[[^\]]*" + re.escape(task) + r"\][^\n]*\n(.*?)(?=\nTASK |\nPLAY RECAP)", stdout, re.S) + s = m and re.search(r"^(ok|changed|skipping|fatal): \[" + re.escape(host) + r"\]", m.group(1), re.M) + return s.group(1) if s else None + + +def changed(stdout, task, host): + return status(stdout, task, host) == "changed" + + +# --- the containers ---------------------------------------------------------------------- + +def compose(*args): + return subprocess.run(COMPOSE + list(args), capture_output=True, text=True) + + +def sql(node, query, timeout=60): + r = subprocess.run(COMPOSE + ["exec", "-T", node, "clickhouse-client", "-u", ADMIN_USER, "--password", + ADMIN_PASSWORD, "--multiquery"], input=query, capture_output=True, text=True, + timeout=timeout) + if r.returncode: + error = "\n".join(l for l in r.stderr.splitlines() if not l.startswith("Emulate Docker CLI")) + raise RuntimeError(f"{node}: {error.strip()[:500]}") + return r.stdout.strip() + + +def wait_ready(node): + for _ in range(120): + try: + if sql(node, "SELECT count() FROM system.zookeeper WHERE path = '/'") != "": + return True + except (RuntimeError, subprocess.TimeoutExpired): + pass + time.sleep(1) + return False + + +def keeper_role(node): + """This node's Keeper state (leader/follower), from the mntr four-letter command.""" + r = subprocess.run(COMPOSE + ["exec", "-T", node, "bash", "-c", + "exec 3<>/dev/tcp/127.0.0.1/9181 && printf mntr >&3 && timeout 3 cat <&3"], + capture_output=True, text=True, timeout=15) + m = re.search(r"zk_server_state\s+(\w+)", r.stdout) + return m.group(1) if m else "unreachable" + + +def restart_one_at_a_time(nodes): + """What a deploy with serial: 1 does after a config change: restart each node and wait + until it's back, its own Keeper too, before the next; then check that inserts work.""" + for n in nodes: + compose("restart", n) + check(wait_ready(n), f"{n} is back after its restart") + keeper = None + for _ in range(60): + keeper = keeper_role(n) + if keeper in ("leader", "follower"): + break + time.sleep(1) + check(keeper in ("leader", "follower"), f"{n}'s Keeper is back in the ensemble ({keeper})") + insert([o for o in nodes if o != n][0], f"after-{n}-restart") + + +def replicas_in_cluster(node): + return sql(node, "SELECT host_name FROM system.clusters WHERE cluster = 'oonidata_cluster' ORDER BY replica_num FORMAT TSV").split() + + +def insert(node, tag): + sql(node, f"INSERT INTO ooni.citizenlab VALUES ('example.org', 'https://example.org/{tag}', 'ZZ', 'TEST')") + + +def citizenlab(node): + sql(node, "SYSTEM SYNC REPLICA ooni.citizenlab", timeout=120) + return sql(node, "SELECT groupArray(url) FROM (SELECT url FROM ooni.citizenlab FINAL ORDER BY url)") + + +def replicated_tables(node): + return sql(node, "SELECT table FROM system.replicas WHERE database = 'ooni' ORDER BY table FORMAT TSV").split() + + +def keeper_replicas(node): + """Replica names under each replicated table's Keeper path, as seen from node.""" + out = {} + for line in sql(node, "SELECT table, zookeeper_path FROM system.replicas WHERE database = 'ooni' FORMAT TSV").splitlines(): + table, path = line.split("\t") + out[table] = sql(node, f"SELECT groupArray(name) FROM (SELECT name FROM system.zookeeper WHERE path = '{path}/replicas' ORDER BY name)") + return out + + +def schema_objects(): + return len(re.findall(r"^CREATE (?:TABLE|MATERIALIZED VIEW) IF NOT EXISTS", open(SCHEMA).read(), re.M)) + + +def on_cluster_ddl(node, name): + sql(node, f"SET distributed_ddl_task_timeout = 60; " + f"CREATE TABLE ooni.{name} ON CLUSTER oonidata_cluster (x UInt8) ENGINE = Memory") + + +# --- the test ---------------------------------------------------------------------------- + +def main(): + global VARS, ADMIN_USER, ADMIN_PASSWORD + os.makedirs(OUT, exist_ok=True) + VARS = write_inventory() + ADMIN_USER = VARS["clickhouse_admin_user"] + ADMIN_PASSWORD = re.fullmatch(r"\{\{ '([^']+)' \}\}", VARS["clickhouse_admin_password"]).group(1) + three = ["data1", "data2", "data3"] + + # A three-node cluster from production's vars + set_cluster(three) + check(ansible("render.yml")[0] == 0, "the clickhouse role renders config.xml and users.xml for data1-3") + compose("up", "-d", "--quiet-pull", *three) + check(all(wait_ready(n) for n in three), "data1-3 start and reach Keeper") + rc, out = ansible("schema.yml") + check(rc == 0, "the schema step runs on data1-3") + check(all(changed(out, "Create the tables and views of schema.sql", HOSTS[n]) for n in three), + "the schema step creates the ooni database on data1-3") + check(status(out, "Check whether the ooni database exists", NON_MEMBER) == "skipping", + "the schema step skips a host that isn't in the cluster") + n_objects = schema_objects() + for n in three: + count = int(sql(n, "SELECT count() FROM system.tables WHERE database = 'ooni'")) + check(count == n_objects, f"{n} has the {n_objects} tables and views of schema.sql ({count})") + rc, out = ansible("schema.yml") + check(rc == 0 and all(status(out, "Create the ooni database", HOSTS[n]) == "skipping" for n in three), + "a second run leaves the existing databases alone") + insert("data1", "before") + check(len({citizenlab(n) for n in three}) == 1, "data1-3 replicate citizenlab") + + # Add data4: edit the cluster in vars, render, restart one at a time, start data4 + set_cluster(three + ["data4"]) + check(ansible("render.yml")[0] == 0, "the clickhouse role renders the configs with data4") + restart_one_at_a_time(three) + compose("up", "-d", "--quiet-pull", "data4") + check(wait_ready("data4"), "data4 starts and reaches Keeper") + for n in three + ["data4"]: + check(replicas_in_cluster(n) == [f"clickhouse{i}.prod.ooni.io" for i in (1, 2, 3, 4)], + f"{n} sees four replicas in oonidata_cluster") + rc, out = ansible("schema.yml") + check(rc == 0 and changed(out, "Create the tables and views of schema.sql", HOSTS["data4"]) + and all(status(out, "Create the ooni database", HOSTS[n]) == "skipping" for n in three), + "the schema step creates the ooni database on data4 alone") + check(replicated_tables("data4") == replicated_tables("data1"), "data4 has every replicated table") + check(all("'04'" in names for names in keeper_replicas("data1").values()), + "Keeper lists replica 04 for every replicated table") + check(citizenlab("data4") == citizenlab("data1"), "data4 catches up on citizenlab") + insert("data4", "from-data4") + check(len({citizenlab(n) for n in three + ["data4"]}) == 1, "an insert on data4 reaches data1-3") + on_cluster_ddl("data1", "ddl_with_four") + check(all(sql(n, "EXISTS TABLE ooni.ddl_with_four") == "1" for n in three + ["data4"]), + "ON CLUSTER DDL reaches data4") + + # Remove data4: edit vars back, render, restart one at a time, stop data4, drop its replica + set_cluster(three) + check(ansible("render.yml")[0] == 0, "the clickhouse role renders the configs without data4") + restart_one_at_a_time(three) + compose("stop", "data4") + sql("data1", "SYSTEM DROP REPLICA '04'") + for n in three: + check(replicas_in_cluster(n) == [f"clickhouse{i}.prod.ooni.io" for i in (1, 2, 3)], + f"{n} sees three replicas in oonidata_cluster") + check(all("'04'" not in names for names in keeper_replicas("data1").values()), + "Keeper has no replica 04 left") + start = time.time() + on_cluster_ddl("data1", "ddl_with_three") + check(time.time() - start < 30 and all(sql(n, "EXISTS TABLE ooni.ddl_with_three") == "1" for n in three), + "ON CLUSTER DDL completes without data4") + insert("data2", "after-removal") + check(len({citizenlab(n) for n in three}) == 1, "data1-3 still replicate") + + # Re-image data4 (empty disk, same replica name) and add it again + compose("rm", "-f", "-s", "-v", "data4") + set_cluster(three + ["data4"]) + check(ansible("render.yml")[0] == 0, "the configs with data4 are rendered again") + restart_one_at_a_time(three) + compose("up", "-d", "data4") + check(wait_ready("data4"), "the re-imaged data4 starts") + rc, out = ansible("schema.yml", limit=HOSTS["data4"]) + check(rc == 0 and changed(out, "Create the tables and views of schema.sql", HOSTS["data4"]), + "the schema step creates the ooni database on the re-imaged data4") + check(citizenlab("data4") == citizenlab("data1"), "the re-imaged data4 catches up") + + print(f"\n{len(failures)} failed" if failures else "\nall passed") + sys.exit(1 if failures else 0) + + +if __name__ == "__main__": + main() From 4af881a48ec1f089ed212b4f1be6e84e9f0674e9 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:23 +0000 Subject: [PATCH 10/16] clickhouse role: restart ClickHouse after an upgrade, and check it's back apt doesn't restart a running clickhouse-server: after a version bump the role installed the new packages and left the old version running, until something else restarted it (tested on Debian 12: 25.3.14.14 installed, 24.8.6.70 still running under the same PID). The package install now notifies Restart-clickhouse, as a changed unit file does (upstream only reloaded, which doesn't apply a changed unit), and the service step restarts right after daemon-reload instead of at the end of the play. It then fails unless the server answers SELECT version() with clickhouse_version, and waits until none of its replicated tables is read-only, i.e. until it's back in Keeper. With serial: 1 or --limit, a host is back on its new version before the next one is deployed. The admin client config the checks use is now written before the service step, in --check too; the version comparison is skipped in --check, where nothing is restarted. --- ansible/roles/clickhouse/README.md | 9 ++++- ansible/roles/clickhouse/defaults/main.yml | 3 +- .../roles/clickhouse/tasks/install-Debian.yml | 3 ++ ansible/roles/clickhouse/tasks/main.yml | 17 ++++---- .../clickhouse/tasks/service/clickhouse.yml | 40 ++++++++++++++++++- 5 files changed, 60 insertions(+), 12 deletions(-) diff --git a/ansible/roles/clickhouse/README.md b/ansible/roles/clickhouse/README.md index e2c54400..cee1d937 100644 --- a/ansible/roles/clickhouse/README.md +++ b/ansible/roles/clickhouse/README.md @@ -18,7 +18,10 @@ here like any other role in this repo. `apt upgrade` can't move them (`tasks/install-Debian.yml`). To upgrade or downgrade, change `clickhouse_version` and rerun the role. 2. Writes `config.xml` and `users.xml` from `templates/` (`tasks/config/clickhouse.yml`) - and the systemd unit (`tasks/service/clickhouse.yml`). + and the systemd unit (`tasks/service/clickhouse.yml`). New packages, config or unit + restart ClickHouse right away, not at the end of the play; the role then fails unless the + server runs `clickhouse_version`, and waits until none of its replicated tables is + read-only, so that with `serial: 1` or `--limit` a host is back before the next one. 3. Applies `clickhouse_custom_users`, `clickhouse_custom_quotas` and `clickhouse_custom_grants` as SQL (`tasks/config/{users,quotas,grants}.yml`, `templates/DDL/`), each behind its `clickhouse_role_manage_*` toggle: @@ -30,7 +33,7 @@ here like any other role in this repo. - users and quotas that aren't in vars are dropped when `clickhouse_sql_objects_drop_unlisted` is true. - The SQL runs through `clickhouse-client --config-file + The SQL, and the checks after a restart, run through `clickhouse-client --config-file {{ clickhouse_admin_client_config }}`, a root-only file with `clickhouse_admin_user`'s credentials, and the statements go in on stdin, so task output isn't hidden. Only at `-vvv` does ansible show the statements, and with them the users' password hashes. @@ -49,6 +52,8 @@ here like any other role in this repo. again. - Upstream put `--password` on every command line and hid all SQL task output with `no_log`. +- Upstream didn't restart ClickHouse after installing new packages, and apt doesn't: a + version bump left the old version running until something else restarted it. - Upstream installed `clickhouse_packages` at `clickhouse_version` but didn't pin them, so `apt upgrade` would install the newest release in the repo (26.8.36.3 instead of 24.8.6.70, checked 2026-10-27). diff --git a/ansible/roles/clickhouse/defaults/main.yml b/ansible/roles/clickhouse/defaults/main.yml index d8c8619f..07ed5ee3 100644 --- a/ansible/roles/clickhouse/defaults/main.yml +++ b/ansible/roles/clickhouse/defaults/main.yml @@ -19,7 +19,8 @@ clickhouse_admin_client_config: /etc/clickhouse-client/ansible-admin.xml clickhouse_documentation_link: https://clickhouse.tech/docs/en/ clickhouse_environment_file: /etc/default/clickhouse -# Set to restart-clickhouse if you need a complete restart of the service i.e. changing listeners config +# Handler for a changed config.xml or users.xml: Restart-clickhouse, or Reload-clickhouse to +# have the server reload them without a restart (not every setting applies on a reload) clickhouse_handler_on_config_change: "Restart-clickhouse" # Owner diff --git a/ansible/roles/clickhouse/tasks/install-Debian.yml b/ansible/roles/clickhouse/tasks/install-Debian.yml index f0f59050..95cdd1b2 100644 --- a/ansible/roles/clickhouse/tasks/install-Debian.yml +++ b/ansible/roles/clickhouse/tasks/install-Debian.yml @@ -76,12 +76,15 @@ mode: "0644" tags: [clickhouse_install] +# A package upgrade doesn't restart the running server: restart it, or it keeps running +# the old version until something else restarts it. - name: CLICKHOUSE | Be sure clickhouse packages are installed (apt) ansible.builtin.apt: name: "{{ clickhouse_packages }}" state: present update_cache: true allow_downgrade: true + notify: Restart-clickhouse tags: [clickhouse_install] - name: CLICKHOUSE | Ensure user files and access control directories exist diff --git a/ansible/roles/clickhouse/tasks/main.yml b/ansible/roles/clickhouse/tasks/main.yml index 70d83923..8c365be3 100644 --- a/ansible/roles/clickhouse/tasks/main.yml +++ b/ansible/roles/clickhouse/tasks/main.yml @@ -10,14 +10,9 @@ tags: - clickhouse_configure -- name: CLICKHOUSE | ClickHouse Service - ansible.builtin.include_tasks: service/clickhouse.yml - tags: - - clickhouse_service - -# The SQL-driven users, quotas and grants below are -# applied with clickhouse-client as the admin user, which reads its credentials -# from this file, so that no password is on a command line or in task output. +# clickhouse-client runs as the admin user, for the checks after a restart and the +# SQL-driven users, quotas and grants, reading its credentials from this file, so +# that no password is on a command line or in task output. - name: CLICKHOUSE | Admin client configuration ansible.builtin.template: src: admin-client.xml.j2 @@ -29,10 +24,16 @@ diff: false check_mode: false # the checks below read it, in --check too tags: + - clickhouse_service - clickhouse_quotas - clickhouse_users - clickhouse_grants +- name: CLICKHOUSE | ClickHouse Service + ansible.builtin.include_tasks: service/clickhouse.yml + tags: + - clickhouse_service + - name: CLICKHOUSE | Configure users ansible.builtin.include_tasks: config/users.yml tags: diff --git a/ansible/roles/clickhouse/tasks/service/clickhouse.yml b/ansible/roles/clickhouse/tasks/service/clickhouse.yml index 6f1afc15..7d7c86d7 100644 --- a/ansible/roles/clickhouse/tasks/service/clickhouse.yml +++ b/ansible/roles/clickhouse/tasks/service/clickhouse.yml @@ -7,7 +7,7 @@ mode: 0644 owner: root group: root - notify: Reload-clickhouse + notify: Restart-clickhouse # a changed unit only applies on a restart - name: CLICKHOUSE | Configuring service ansible.builtin.systemd: @@ -16,6 +16,11 @@ enabled: "{{ clickhouse_service_enabled }}" daemon_reload: yes +# Restart now (after daemon-reload) for new packages, config or unit, not at the end of +# the play, so that the checks below see the restarted server before the next host. +- name: CLICKHOUSE | Restart for changed packages, config or unit + ansible.builtin.meta: flush_handlers + - name: CLICKHOUSE | Wait for service listening ansible.builtin.wait_for: host: localhost @@ -26,3 +31,36 @@ when: - clickhouse_service_enabled - clickhouse_service_state == "started" + +- name: CLICKHOUSE | Check that the server runs clickhouse_version + ansible.builtin.command: + cmd: clickhouse-client --config-file {{ clickhouse_admin_client_config }} --query "SELECT version()" + register: clickhouse_running_version + until: clickhouse_running_version.rc == 0 + retries: 60 + delay: 5 + changed_when: false + check_mode: false # read-only + failed_when: clickhouse_running_version.rc != 0 or clickhouse_running_version.stdout != clickhouse_version + when: + - clickhouse_service_enabled + - clickhouse_service_state == "started" + - not ansible_check_mode # the new version only runs after a real restart + +# After a restart, replicated tables are read-only until the server is back in Keeper: +# wait for that, so that the next host isn't restarted while this one is catching up. +# A new host has no replicated tables yet, and passes at once. +- name: CLICKHOUSE | Wait until no replicated table is read-only + ansible.builtin.command: + cmd: >- + clickhouse-client --config-file {{ clickhouse_admin_client_config }} + --query "SELECT count() FROM system.replicas WHERE is_readonly OR is_session_expired" + register: clickhouse_readonly_replicas + until: clickhouse_readonly_replicas.rc == 0 and clickhouse_readonly_replicas.stdout == "0" + retries: 60 + delay: 5 + changed_when: false + check_mode: false # read-only + when: + - clickhouse_service_enabled + - clickhouse_service_state == "started" From 7b3fd73bee5a8b5a5548151dd9ca8d1c04a03eed Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Tue, 27 Oct 2026 14:06:23 +0000 Subject: [PATCH 11/16] clickhouse role: test rolling a version bump out one host at a time tests/upgrade/run.sh installs FROM_VERSION with the role on three Debian 12 hosts with systemd, named like the production replicas, with the real vars and the apt source line the role's upstream version left, then sets clickhouse_version to TO_VERSION (by default tests/upgrade/next_version, the next hop of the plan in #477) and deploys each host with --limit in turn, while a writer inserts throughout. After each host it checks that the role succeeded, that ClickHouse is active under systemd on the new version with its packages pinned, and that every node answers and reaches Keeper, and it reports each node's version, Keeper role and the inserts that failed meanwhile. At the end every node must run TO_VERSION, take inserts, have no read-only or stuck replicated tables, and have every row whose insert succeeded. 24.8.6.70 -> 25.3.14.14 and 25.3.14.14 -> 25.8.29.51 pass; one insert failed in all, to the node that was restarting. CI runs it when a PR changes clickhouse_version (from the base's version to the PR's) or the role, other than its README and other tests, and by hand with any two versions; not for other vars changes. The runbook says how to upgrade. --- .github/workflows/test_clickhouse_upgrade.yml | 70 +++++ .../clickhouse/tests/upgrade/.dockerignore | 2 + .../roles/clickhouse/tests/upgrade/.gitignore | 2 + .../roles/clickhouse/tests/upgrade/Dockerfile | 8 + .../roles/clickhouse/tests/upgrade/README.md | 36 +++ .../clickhouse/tests/upgrade/compose.yml | 29 ++ .../roles/clickhouse/tests/upgrade/deploy.yml | 13 + .../clickhouse/tests/upgrade/next_version | 5 + ansible/roles/clickhouse/tests/upgrade/run.sh | 20 ++ .../clickhouse/tests/upgrade/test_upgrade.py | 267 ++++++++++++++++++ docs/Runbooks.md | 21 ++ 11 files changed, 473 insertions(+) create mode 100644 .github/workflows/test_clickhouse_upgrade.yml create mode 100644 ansible/roles/clickhouse/tests/upgrade/.dockerignore create mode 100644 ansible/roles/clickhouse/tests/upgrade/.gitignore create mode 100644 ansible/roles/clickhouse/tests/upgrade/Dockerfile create mode 100644 ansible/roles/clickhouse/tests/upgrade/README.md create mode 100644 ansible/roles/clickhouse/tests/upgrade/compose.yml create mode 100644 ansible/roles/clickhouse/tests/upgrade/deploy.yml create mode 100644 ansible/roles/clickhouse/tests/upgrade/next_version create mode 100755 ansible/roles/clickhouse/tests/upgrade/run.sh create mode 100755 ansible/roles/clickhouse/tests/upgrade/test_upgrade.py diff --git a/.github/workflows/test_clickhouse_upgrade.yml b/.github/workflows/test_clickhouse_upgrade.yml new file mode 100644 index 00000000..a80607da --- /dev/null +++ b/.github/workflows/test_clickhouse_upgrade.yml @@ -0,0 +1,70 @@ +# Rolls a ClickHouse version bump out with the clickhouse role, one host at a time +# (ansible/roles/clickhouse/tests/upgrade). Slow (it installs ClickHouse twice on three hosts), +# so it only runs when it has something to test: +# - a PR that changes clickhouse_version: from the base's version to the PR's; +# - a PR that changes the role (other than its README and other tests), the schema step or +# this workflow: from production's version to tests/upgrade/next_version; +# - by hand, with any two versions. +# A change to the vars alone, e.g. a user's settings, doesn't run it. +name: Test ClickHouse upgrade +on: + pull_request: + paths: + - "ansible/group_vars/clickhouse/vars.yml" + - "ansible/roles/clickhouse/**" + - "!ansible/roles/clickhouse/README.md" + - "!ansible/roles/clickhouse/tests/**" + - "ansible/roles/clickhouse/tests/upgrade/**" + - "ansible/roles/oonidata_clickhouse/tasks/schema.yml" + - ".github/workflows/test_clickhouse_upgrade.yml" + workflow_dispatch: + inputs: + from_version: {description: "Version to install first (default: production's)", required: false} + to_version: {description: "Version to upgrade to (default: tests/upgrade/next_version)", required: false} + +jobs: + upgrade: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + with: {fetch-depth: 0} + - name: Choose the versions + id: versions + env: + EVENT: ${{ github.event_name }} + BASE: ${{ github.event.pull_request.base.sha }} + FROM_INPUT: ${{ inputs.from_version }} + TO_INPUT: ${{ inputs.to_version }} + run: | + # clickhouse_version's value, without quotes or a trailing comment + version() { sed -n 's/^clickhouse_version: *["'"'"']\{0,1\}\([0-9.]*\).*/\1/p'; } + run=true from=$FROM_INPUT to=$TO_INPUT + if [ "$EVENT" = pull_request ]; then + base=$(git show "$BASE:ansible/group_vars/clickhouse/vars.yml" | version) + head=$(version < ansible/group_vars/clickhouse/vars.yml) + if [ "$base" != "$head" ]; then + from=$base to=$head + else + # the files of `paths:` above, other than the vars + changed=$(git diff --name-only "$BASE" HEAD \ + | grep -E '^(ansible/roles/clickhouse/|ansible/roles/oonidata_clickhouse/tasks/schema\.yml$|\.github/workflows/test_clickhouse_upgrade\.yml$)' \ + | grep -v -E '^ansible/roles/clickhouse/(README\.md$|tests/)' ; \ + git diff --name-only "$BASE" HEAD | grep '^ansible/roles/clickhouse/tests/upgrade/' || true) + [ -n "$changed" ] || run=false # only the vars changed, not the version + fi + fi + echo "run=$run" >> "$GITHUB_OUTPUT" + echo "from=$from" >> "$GITHUB_OUTPUT" + echo "to=$to" >> "$GITHUB_OUTPUT" + echo "run=$run from=${from:-production} to=${to:-next_version}" + - if: steps.versions.outputs.run == 'true' + run: pipx install ansible-core==2.17.7 && sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends python3-yaml + - if: steps.versions.outputs.run == 'true' + env: + FROM_VERSION: ${{ steps.versions.outputs.from }} + TO_VERSION: ${{ steps.versions.outputs.to }} + run: ansible/roles/clickhouse/tests/upgrade/run.sh + - if: failure() + uses: actions/upload-artifact@v4 + with: {name: upgrade-out, path: ansible/roles/clickhouse/tests/upgrade/out/} diff --git a/ansible/roles/clickhouse/tests/upgrade/.dockerignore b/ansible/roles/clickhouse/tests/upgrade/.dockerignore new file mode 100644 index 00000000..d8a1d071 --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/.dockerignore @@ -0,0 +1,2 @@ +cache/ +out/ diff --git a/ansible/roles/clickhouse/tests/upgrade/.gitignore b/ansible/roles/clickhouse/tests/upgrade/.gitignore new file mode 100644 index 00000000..a97499d0 --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/.gitignore @@ -0,0 +1,2 @@ +out/ +cache/ diff --git a/ansible/roles/clickhouse/tests/upgrade/Dockerfile b/ansible/roles/clickhouse/tests/upgrade/Dockerfile new file mode 100644 index 00000000..05fdd2aa --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/Dockerfile @@ -0,0 +1,8 @@ +# A Debian 12 host with systemd, like the ClickHouse servers, for the role to install on +FROM docker.io/library/debian:12 +RUN apt-get update -qq \ + && apt-get install -y -qq --no-install-recommends systemd systemd-sysv python3 ca-certificates sudo >/dev/null \ + && rm -rf /var/lib/apt/lists/* \ + && rm -f /etc/apt/apt.conf.d/docker-clean # keep downloaded packages in the shared cache +STOPSIGNAL SIGRTMIN+3 +CMD ["/sbin/init"] diff --git a/ansible/roles/clickhouse/tests/upgrade/README.md b/ansible/roles/clickhouse/tests/upgrade/README.md new file mode 100644 index 00000000..36929b38 --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/README.md @@ -0,0 +1,36 @@ +# Upgrade test + +`run.sh` (`test_upgrade.py`) rolls a ClickHouse version bump out across the cluster with the +role, one host at a time, the way it's done in production: + +1. Installs `FROM_VERSION` (default: production's `clickhouse_version`) on three Debian 12 + hosts with systemd, named like the production replicas (`compose.yml`), with the real + group and host vars (secrets replaced by test values) and `deploy.yml`, which is what + `deploy-clickhouse.yml` does to a replica without the nftables rules. As for a new cluster, + the hosts are deployed with `--skip-tags clickhouse_schema` first, and the `ooni` database is + created once Keeper has a quorum. +2. Sets `clickhouse_version` to `TO_VERSION` (default: `next_version`, the next hop of the + upgrade plan in ooni/devops#477), then deploys each host with `--limit`, in turn, while a + writer inserts a row every half second, on each node in turn. The hosts start with the + apt source line the role's upstream version left, as production's do. + +After each host it checks that the role succeeded, which includes the role's own check that +the server runs `clickhouse_version`; that the host's `clickhouse-server` is active under +systemd and its packages are `TO_VERSION`, pinned; and that every node answers and reaches +Keeper. It prints each node's version and Keeper role (leader or follower) and how many inserts +failed meanwhile: errors while versions are mixed are expected for some hops (see #477). At +the end every node must run `TO_VERSION`, take inserts, have no read-only or failing +replicated tables, and have every row whose insert succeeded. + + FROM_VERSION=25.3.14.14 TO_VERSION=25.8.29.51 ./run.sh + +Needs docker compose (or rootless podman with +`DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock`), ansible-playbook (the +community.docker collection is installed into `cache/` if missing) and PyYAML, and network +access to packages.clickhouse.com. Downloaded packages are kept in `cache/debs` and copied +into each host's own apt cache before it's deployed, so each version is downloaded once. +`KEEP=1` leaves the hosts running. The ansible output of each deploy is in `out/`. + +In CI (`.github/workflows/test_clickhouse_upgrade.yml`) it runs when a PR changes +`clickhouse_version` (from the base's version to the PR's) or how the role installs or +restarts ClickHouse, and by hand with any two versions. diff --git a/ansible/roles/clickhouse/tests/upgrade/compose.yml b/ansible/roles/clickhouse/tests/upgrade/compose.yml new file mode 100644 index 00000000..75204370 --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/compose.yml @@ -0,0 +1,29 @@ +# Three Debian hosts with systemd, named like the production replicas (hostname and alias as +# in ../../../oonidata_clickhouse/tests/compose.yml), that ansible reaches with the +# community.docker connection and installs ClickHouse on with the role. The test copies the +# downloaded packages through cache/debs from host to host, so that each version is +# downloaded once (sharing apt's cache itself would make the hosts wait for its lock). +x-host: &host + build: . + image: localhost/clickhouse-role-upgrade-host + privileged: true # systemd + pids_limit: -1 # ClickHouse starts hundreds of threads + tmpfs: ["/run", "/run/lock"] + volumes: ["./cache/debs:/debs:z"] + mem_limit: 2g +services: + data1: + <<: *host + container_name: chupgrade-data1 + hostname: clickhouse1.prod.ooni.io + networks: {default: {aliases: [data1.htz-fsn.prod.ooni.nu, clickhouse1.prod.ooni.io]}} + data2: + <<: *host + container_name: chupgrade-data2 + hostname: clickhouse2.prod.ooni.io + networks: {default: {aliases: [data2.htz-fsn.prod.ooni.nu, clickhouse2.prod.ooni.io]}} + data3: + <<: *host + container_name: chupgrade-data3 + hostname: clickhouse3.prod.ooni.io + networks: {default: {aliases: [data3.htz-fsn.prod.ooni.nu, clickhouse3.prod.ooni.io]}} diff --git a/ansible/roles/clickhouse/tests/upgrade/deploy.yml b/ansible/roles/clickhouse/tests/upgrade/deploy.yml new file mode 100644 index 00000000..a386a0db --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/deploy.yml @@ -0,0 +1,13 @@ +# What deploy-clickhouse.yml does to a replica, one host at a time: the clickhouse role, then +# oonidata_clickhouse's schema step. Not oonidata_clickhouse's nftables rules, which only +# let production's addresses in. +- hosts: clickhouse + serial: 1 # as deploy-clickhouse.yml + gather_facts: true + vars: + oonidata_clickhouse_schema: "{{ lookup('env', 'SCHEMA') }}" + roles: + - clickhouse + tasks: + - ansible.builtin.include_role: {name: oonidata_clickhouse, tasks_from: schema.yml} + tags: [clickhouse_schema] diff --git a/ansible/roles/clickhouse/tests/upgrade/next_version b/ansible/roles/clickhouse/tests/upgrade/next_version new file mode 100644 index 00000000..1b46a4b2 --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/next_version @@ -0,0 +1,5 @@ +# The version the upgrade test upgrades to when a change doesn't bump clickhouse_version: +# the next hop after production's, from the upgrade plan in ooni/devops#477 (for #437): +# 24.8.6.70 -> 25.3.14.14 -> 25.8.29.51 -> 26.3.17.110 -> 26.8.9.10 +# Move it on when production's clickhouse_version reaches it. +25.3.14.14 diff --git a/ansible/roles/clickhouse/tests/upgrade/run.sh b/ansible/roles/clickhouse/tests/upgrade/run.sh new file mode 100755 index 00000000..53563f2e --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Rolls a ClickHouse version bump out with the role, one host at a time (test_upgrade.py): +# FROM_VERSION=24.8.6.70 TO_VERSION=25.3.14.14 ./run.sh +# Needs docker compose, ansible-playbook and PyYAML, and network access to +# packages.clickhouse.com; the packages are kept in cache/. With rootless podman, point +# DOCKER_HOST at its socket. KEEP=1 leaves the hosts running. +set -euo pipefail +cd "$(dirname "$0")" +export COMPOSE=${COMPOSE:-docker compose} +compose() { $COMPOSE -f compose.yml "$@"; } +journals() { for n in data1 data2 data3; do echo "--- $n"; compose exec -T $n journalctl -u clickhouse-server --no-pager -n 30 2>/dev/null; done; } +trap 'rc=$?; [ $rc = 0 ] || journals; [ -n "${KEEP:-}" ] || compose down -v --remove-orphans >/dev/null 2>&1' EXIT +compose down -v --remove-orphans >/dev/null 2>&1 # left from a run with KEEP=1 +rm -rf out && mkdir -p out cache +if ! ansible-galaxy collection list community.docker 2>/dev/null | grep -q community.docker; then + # the version the repo pins + ansible-galaxy collection install -p cache/collections -r ../../../../requirements/ansible-galaxy.yml >/dev/null + export ANSIBLE_COLLECTIONS_PATH="$PWD/cache/collections:${ANSIBLE_COLLECTIONS_PATH:-}" +fi +python3 test_upgrade.py diff --git a/ansible/roles/clickhouse/tests/upgrade/test_upgrade.py b/ansible/roles/clickhouse/tests/upgrade/test_upgrade.py new file mode 100755 index 00000000..ef6ed6e2 --- /dev/null +++ b/ansible/roles/clickhouse/tests/upgrade/test_upgrade.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +""" +Rolls a ClickHouse version bump out across the cluster with the role, one host at a time, +as an operator would: deploy FROM_VERSION to three Debian hosts named like the production +replicas (compose.yml), with the real group and host vars (secrets replaced by test values), +then set clickhouse_version to TO_VERSION and deploy each host with --limit, in turn. + +After each host it checks that the role succeeded, that the host's ClickHouse runs +TO_VERSION under systemd with its packages pinned to it, and that every node still answers +and reaches Keeper; and it reports what the cluster looks like in between: each node's +version and Keeper role, and how many of the inserts made meanwhile failed (mixed-version +errors are expected for some hops; see ooni/devops#477). At the end every +node must run TO_VERSION, have no read-only or failing replicated tables, and have every +row whose insert succeeded. + +The hosts start with the apt source line the role's upstream version left (no signed-by), +as production's do. FROM_VERSION defaults to production's clickhouse_version, TO_VERSION to +next_version. Called by run.sh; needs ansible-playbook with community.docker, and PyYAML. +""" +import os, re, shutil, subprocess, sys, threading, time +import yaml + +TESTS = os.path.dirname(os.path.abspath(__file__)) +ANSIBLE = os.path.normpath(os.path.join(TESTS, "../../../..")) +REPO = os.path.dirname(ANSIBLE) +OUT = os.path.join(TESTS, "out") +INV = os.path.join(OUT, "inventory") +COMPOSE = os.environ.get("COMPOSE", "docker compose").split() + ["-f", os.path.join(TESTS, "compose.yml")] +NODES = {"data1": "data1.htz-fsn.prod.ooni.nu", "data2": "data2.htz-fsn.prod.ooni.nu", + "data3": "data3.htz-fsn.prod.ooni.nu"} +ADMIN_CONFIG = "/etc/clickhouse-client/ansible-admin.xml" # written by the role +failures = [] + + +def check(ok, what): + print(("ok " if ok else "FAIL ") + what, flush=True) + if not ok: + failures.append(what) + + +# --- inventory: production's vars, with the version the test sets ------------------------ + +def secrets_replaced(text): + return re.sub(r"lookup\('amazon\.aws\.aws_(?:ssm|secret)', '([^']+)'[^)]*\)", + lambda m: "'test-" + m.group(1).rsplit("/", 1)[-1] + "'", text) + + +def write_inventory(): + shutil.rmtree(INV, ignore_errors=True) + os.makedirs(os.path.join(INV, "group_vars")) + os.makedirs(os.path.join(INV, "host_vars")) + for src, dst in (("group_vars/all/vars.yml", "group_vars/all.yml"), + ("group_vars/clickhouse/vars.yml", "group_vars/clickhouse.yml")): + open(os.path.join(INV, dst), "w").write(secrets_replaced(open(os.path.join(ANSIBLE, src)).read())) + for node, host in NODES.items(): + src = os.path.join(ANSIBLE, "host_vars", host) + text = secrets_replaced(open(src).read()) if os.path.isfile(src) else "" + text += f"\nansible_connection: community.docker.docker\nansible_host: chupgrade-{node}\n" + open(os.path.join(INV, "host_vars", host + ".yml"), "w").write(text) + open(os.path.join(INV, "hosts.ini"), "w").write("[clickhouse]\n" + "\n".join(NODES.values()) + "\n") + + +def set_version(version): + """clickhouse_version in the group vars, as the version bump in a PR would change it.""" + p = os.path.join(INV, "group_vars/clickhouse.yml") + text, n = re.subn(r"^clickhouse_version: .*$", f"clickhouse_version: {version}", open(p).read(), flags=re.M) + assert n == 1 + open(p, "w").write(text) + + +def deploy(label, limit=None, args=()): + hosts = [n for n, h in NODES.items() if limit in (None, h)] + for n in hosts: # the packages downloaded by earlier hosts + sh(n, "cp -n /debs/*.deb /var/cache/apt/archives/ 2>/dev/null; true") + cmd = ["ansible-playbook", "-i", os.path.join(INV, "hosts.ini"), os.path.join(TESTS, "deploy.yml"), *args] + if limit: + cmd += ["--limit", limit] + env = dict(os.environ, ANSIBLE_ROLES_PATH=os.path.join(ANSIBLE, "roles"), ANSIBLE_NOCOLOR="1", + SCHEMA=os.path.join(REPO, "scripts/cluster-migration/schema.sql")) + started = time.time() + r = subprocess.run(cmd, env=env, capture_output=True, text=True) + open(os.path.join(OUT, f"ansible-{label}.log"), "w").write(r.stdout + r.stderr) + for n in hosts: + sh(n, "cp -n /var/cache/apt/archives/*.deb /debs/ 2>/dev/null; true") + if r.returncode: + print(r.stdout[-4000:] + r.stderr[-1500:]) + return r.returncode, time.time() - started + + +# --- the hosts ----------------------------------------------------------------------------- + +def podman_noise_removed(stderr): + """stderr without the notices of podman's docker emulation, if that's what runs compose""" + noise = ("Emulate Docker CLI", "Executing external compose provider", "Error: executing ") + return "\n".join(l for l in stderr.splitlines() if not any(n in l for n in noise) and l.strip("\x1b[0m ")).strip() + + +def sh(node, command, timeout=60): + r = subprocess.run(COMPOSE + ["exec", "-T", node, "bash", "-c", command], capture_output=True, + text=True, timeout=timeout) + return r.returncode, r.stdout.strip(), podman_noise_removed(r.stderr) + + +def sql(node, query, timeout=60): + r = subprocess.run(COMPOSE + ["exec", "-T", node, "clickhouse-client", "--config-file", ADMIN_CONFIG, + "--multiquery"], input=query, capture_output=True, text=True, timeout=timeout) + if r.returncode: + raise RuntimeError(podman_noise_removed(r.stderr)[:300]) + return r.stdout.strip() + + +def try_sql(node, query, timeout=30): + try: + return sql(node, query, timeout) + except (RuntimeError, subprocess.TimeoutExpired) as e: + return f"error: {str(e).splitlines()[0] if str(e) else 'timeout'}" + + +def keeper_role(node): + """This node's Keeper state (leader/follower), from the mntr four-letter command.""" + rc, out, _ = sh(node, "exec 3<>/dev/tcp/127.0.0.1/9181 && printf mntr >&3 && timeout 3 cat <&3", timeout=10) + m = re.search(r"zk_server_state\s+(\w+)", out) + return m.group(1) if m else "unreachable" + + +def snapshot(): + return {n: (try_sql(n, "SELECT version()"), keeper_role(n)) for n in NODES} + + +class Writer(threading.Thread): + """Inserts a row into citizenlab every half second, on each node in turn, and keeps the + urls whose insert succeeded, and the errors of those that didn't.""" + def __init__(self): + super().__init__(daemon=True) + self.ok, self.errors, self.stop, self.i = [], [], threading.Event(), 0 + + def run(self): + nodes = list(NODES) + while not self.stop.is_set(): + node = nodes[self.i % len(nodes)] + url = f"https://example.org/w{self.i}" + r = try_sql(node, f"INSERT INTO ooni.citizenlab VALUES ('example.org', '{url}', 'ZZ', 'TEST')") + (self.errors.append((node, r)) if r.startswith("error") else self.ok.append(url)) + self.i += 1 + time.sleep(0.5) + + def take(self): + ok, errors = len(self.ok), list(self.errors) + self.errors.clear() + return ok, errors + + +def report(title, writer=None, ok_before=0): + print(f"\n--- {title}") + for n, (version, keeper) in snapshot().items(): + print(f" {n}: ClickHouse {version}, Keeper {keeper}") + if writer: + ok, errors = writer.take() + print(f" inserts: {ok - ok_before} succeeded, {len(errors)} failed") + for node, e in errors[:5]: + print(f" {node}: {e[:160]}") + return ok + return ok_before + + +# --- the test ------------------------------------------------------------------------------ + +def main(): + os.makedirs(OUT, exist_ok=True) + os.makedirs(os.path.join(TESTS, "cache/debs"), exist_ok=True) + write_inventory() + prod = yaml.safe_load(open(os.path.join(INV, "group_vars/clickhouse.yml")))["clickhouse_version"] + from_version = os.environ.get("FROM_VERSION") or prod + to_version = os.environ.get("TO_VERSION") or next_version() + print(f"upgrading {from_version} -> {to_version}") + if from_version == to_version: + sys.exit(f"nothing to upgrade: FROM_VERSION and TO_VERSION are both {from_version}; " + "move next_version on") + + set_version(from_version) + r = compose("up", "-d", "--build", "--quiet-pull") + check(r.returncode == 0, "the three hosts start") + if r.returncode: + sys.exit(r.stderr[-2000:]) + for n in NODES: + # "degraded" is normal in a container (e.g. kernel mounts it can't have) + rc, state, _ = sh(n, "systemctl is-system-running --wait", timeout=120) + check(state in ("running", "degraded"), f"{n} boots with systemd ({state})") + # what the role's upstream version left on production's hosts: the repository + # without signed-by, and its key among apt's trusted keys (it used apt-key) + compose("cp", os.path.join(TESTS, "../../files/clickhouse-repo-keyring.asc"), f"{n}:/etc/apt/trusted.gpg.d/clickhouse.asc") + sh(n, "echo 'deb https://packages.clickhouse.com/deb lts main' > /etc/apt/sources.list.d/packages_clickhouse_com_deb.list") + # A new cluster: the hosts one at a time, then the ooni database once Keeper has a quorum + rc, took = deploy("from", args=["--skip-tags", "clickhouse_schema"]) + check(rc == 0, f"the role deploys {from_version} to the three hosts, one at a time ({took:.0f}s)") + rc2, _ = deploy("schema", args=["--tags", "clickhouse_schema"]) + check(rc2 == 0, "the schema step creates the ooni database on the three hosts") + if rc or rc2: + sys.exit(1) + for n in NODES: + check(try_sql(n, "SELECT version()") == from_version, f"{n} runs {from_version}") + sql("data1", "INSERT INTO ooni.citizenlab VALUES ('example.org', 'https://example.org/before', 'ZZ', 'TEST')") + report("before the upgrade") + + writer = Writer() + writer.start() + ok_so_far = 0 + set_version(to_version) + for i, (n, host) in enumerate(NODES.items(), 1): + rc, took = deploy(f"to-{n}", limit=host) + check(rc == 0, f"step {i}: the role upgrades {n} and finds it running {to_version} ({took:.0f}s)") + check(try_sql(n, "SELECT version()") == to_version, f"step {i}: {n} runs {to_version}") + rc, active, _ = sh(n, "systemctl is-active clickhouse-server") + check(active == "active", f"step {i}: clickhouse-server is active under systemd on {n}") + rc, pin, _ = sh(n, "grep '^Pin:' /etc/apt/preferences.d/clickhouse; dpkg-query -W -f '${Version}' clickhouse-server") + check(f"Pin: version {to_version}" in pin and pin.endswith(to_version), f"step {i}: {n}'s packages are {to_version}, pinned") + for other in NODES: + check(wait_keeper(other), f"step {i}: {other} answers and reaches Keeper") + ok_so_far = report(f"after step {i} ({n} upgraded)", writer, ok_so_far) + time.sleep(5) + writer.stop.set() + writer.join() + report("after the upgrade", writer, ok_so_far) + + check(len(writer.ok) > 20, f"inserts kept working during the rollout ({len(writer.ok)} succeeded)") + for n in NODES: # after the rollout, every node takes inserts + url = f"https://example.org/after-{n}" + check(not try_sql(n, f"INSERT INTO ooni.citizenlab VALUES ('example.org', '{url}', 'ZZ', 'TEST')").startswith("error"), + f"{n} takes inserts after the upgrade") + writer.ok.append(url) + for n in NODES: + check(try_sql(n, "SELECT version()") == to_version, f"{n} runs {to_version}") + sql(n, "SYSTEM SYNC REPLICA ooni.citizenlab", timeout=300) + bad = sql(n, "SELECT count() FROM system.replicas WHERE is_readonly OR is_session_expired") + check(bad == "0", f"{n} has no read-only replicated tables") + stuck = sql(n, "SELECT count() FROM system.replication_queue WHERE num_tries > 10") + check(stuck == "0", f"{n} has no replication tasks failing over and over") + urls = set(sql(n, "SELECT url FROM ooni.citizenlab FINAL").split()) + check("https://example.org/before" in urls, f"{n} has the row inserted before the upgrade") + missing = [u for u in writer.ok if u not in urls] + check(not missing, f"{n} has all {len(writer.ok)} rows whose insert succeeded (missing: {missing[:3]})") + + print(f"\n{len(failures)} failed" if failures else "\nall passed") + sys.exit(1 if failures else 0) + + +def next_version(): + lines = [l.strip() for l in open(os.path.join(TESTS, "next_version")) if l.strip() and not l.startswith("#")] + return lines[0] + + +def wait_keeper(node, seconds=60): + """Whether node answers and reaches Keeper within `seconds` (a Keeper election may be + under way after a restart).""" + for _ in range(seconds // 2): + if try_sql(node, "SELECT count() FROM system.zookeeper WHERE path = '/'").isdigit(): + return True + time.sleep(2) + return False + + +def compose(*args): + return subprocess.run(COMPOSE + list(args), capture_output=True, text=True) + + +if __name__ == "__main__": + main() diff --git a/docs/Runbooks.md b/docs/Runbooks.md index 88a52eb8..6085f498 100644 --- a/docs/Runbooks.md +++ b/docs/Runbooks.md @@ -1185,6 +1185,27 @@ PARTITION BY concat(substring(bucket_date, 1, 4), substring(bucket_date, 6, 2)) PRIMARY KEY (measurement_uid, observation_idx) ORDER BY (measurement_uid, observation_idx, measurement_start_time, hostname) SETTINGS index_granularity = 8192 ``` +### Upgrading ClickHouse + +ClickHouse is upgraded one hop at a time, along the plan of the upgrade test in +ooni/devops#477 (for #437), whose next hop `ansible/roles/clickhouse/tests/upgrade/next_version` +names: + +1. Change `clickhouse_version` in `ansible/group_vars/clickhouse/vars.yml` in a PR. CI + (`test_clickhouse_upgrade.yml`) rolls that bump out on a test cluster, one host at a time, + and shows what each step looks like. +2. Deploy one host at a time, and look at the cluster before going on to the next: + ``` + ./play deploy-clickhouse.yml -l data1.htz-fsn.prod.ooni.nu --diff -i inventory + ``` + The role installs the new packages, pins them to the new version in + `/etc/apt/preferences.d/clickhouse` (so `apt upgrade` can't move them), restarts + ClickHouse, fails unless the server then runs `clickhouse_version`, and waits until none + of its replicated tables is read-only, i.e. until it's back in Keeper. + +Until every replica has been upgraded, some hops log errors from the replicas that run the +other version; finish the rollout rather than leaving the cluster half upgraded. + ### Adding and removing a replica The cluster's replicas are defined in `ansible/group_vars/clickhouse/vars.yml`: From 091e3e646000eb9653a6291030bd8f727acd338b Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Wed, 28 Oct 2026 09:05:46 +0000 Subject: [PATCH 12/16] clickhouse role tests: create users before quotas, as the role does A quota's TO names users, so the test's order failed once a quota in vars had one. --- ansible/roles/clickhouse/tests/play.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/roles/clickhouse/tests/play.yml b/ansible/roles/clickhouse/tests/play.yml index 46b96885..9bdf27a9 100644 --- a/ansible/roles/clickhouse/tests/play.yml +++ b/ansible/roles/clickhouse/tests/play.yml @@ -12,6 +12,7 @@ - name: Admin client configuration ansible.builtin.template: {src: ../templates/admin-client.xml.j2, dest: "{{ clickhouse_admin_client_config }}", mode: "0644"} no_log: true - - {name: quotas, include_role: {name: clickhouse, tasks_from: config/quotas.yml}, when: clickhouse_role_manage_quotas} + # users first, as in tasks/main.yml: a quota's TO names users - {name: users, include_role: {name: clickhouse, tasks_from: config/users.yml}, when: clickhouse_role_manage_users} + - {name: quotas, include_role: {name: clickhouse, tasks_from: config/quotas.yml}, when: clickhouse_role_manage_quotas} - {name: grants, include_role: {name: clickhouse, tasks_from: config/grants.yml}, when: clickhouse_role_manage_grants} From 3ad48f2e1d7b46d675a6f789687ccb5f5ca21da0 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Wed, 28 Oct 2026 09:05:46 +0000 Subject: [PATCH 13/16] deploy-clickhouse: leave the notebook out; no SQL quotas on it notebook.ooni.org isn't in [clickhouse], so the cluster deploy would have installed the role's default version there. notebook1 stays in the group for its vars (clickhouse_version, the cluster's remote_servers and Keeper), with SQL quotas off like users and grants. --- ansible/deploy-clickhouse.yml | 2 +- ansible/host_vars/notebook1.htz-fsn.prod.ooni.nu | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/deploy-clickhouse.yml b/ansible/deploy-clickhouse.yml index 3477006a..b40882a6 100644 --- a/ansible/deploy-clickhouse.yml +++ b/ansible/deploy-clickhouse.yml @@ -1,7 +1,7 @@ --- - name: Deploy oonidata clickhouse hosts + # Not the notebook: its ClickHouse isn't part of the cluster. hosts: - - notebook.ooni.org - data1.htz-fsn.prod.ooni.nu - data2.htz-fsn.prod.ooni.nu - data3.htz-fsn.prod.ooni.nu diff --git a/ansible/host_vars/notebook1.htz-fsn.prod.ooni.nu b/ansible/host_vars/notebook1.htz-fsn.prod.ooni.nu index 3e31da67..35dcb69c 100644 --- a/ansible/host_vars/notebook1.htz-fsn.prod.ooni.nu +++ b/ansible/host_vars/notebook1.htz-fsn.prod.ooni.nu @@ -152,6 +152,7 @@ clickhouse_listen_hosts: # this only applies to SQL managed users clickhouse_role_manage_users: false +clickhouse_role_manage_quotas: false clickhouse_role_manage_grants: false # these are handled via config files clickhouse_default_users: From 5d070fd701e078e4f4b6176e78bbf71412534776 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Wed, 28 Oct 2026 09:05:46 +0000 Subject: [PATCH 14/16] clickhouse: a small quota for oonitestlists --- ansible/group_vars/clickhouse/vars.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ansible/group_vars/clickhouse/vars.yml b/ansible/group_vars/clickhouse/vars.yml index a8de0db9..2367d054 100644 --- a/ansible/group_vars/clickhouse/vars.yml +++ b/ansible/group_vars/clickhouse/vars.yml @@ -303,9 +303,12 @@ clickhouse_custom_quotas: # dropped if clickhouse_sql_objects_drop_unlisted is turned on - name: ooniprobe - name: oonirun - # TODO: the old vars had oonimeasurements' limits here too, never applied anywhere - - name: oonitestlists - name: notebook + # The test lists editor only inserts url_priorities rows, so a small quota + - name: oonitestlists + intervals: + - "INTERVAL 3600 second MAX queries = 1000, errors = 100, execution_time = 600" + to: [oonitestlists] clickhouse_custom_grants: - databases: [ooni, oonitest] From 201b2dc65e8769bf27d81fedbfa9d243011f5f1b Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Wed, 28 Oct 2026 09:50:35 +0000 Subject: [PATCH 15/16] clickhouse tests: IPv6 on the compose network The role's config listens on "::" only, as in production. Docker turns IPv6 off in containers on an IPv4-only network, so on GitHub Actions ClickHouse couldn't listen and exited (code 210). --- ansible/roles/clickhouse/tests/upgrade/compose.yml | 5 +++++ ansible/roles/oonidata_clickhouse/tests/compose.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ansible/roles/clickhouse/tests/upgrade/compose.yml b/ansible/roles/clickhouse/tests/upgrade/compose.yml index 75204370..10486509 100644 --- a/ansible/roles/clickhouse/tests/upgrade/compose.yml +++ b/ansible/roles/clickhouse/tests/upgrade/compose.yml @@ -27,3 +27,8 @@ services: container_name: chupgrade-data3 hostname: clickhouse3.prod.ooni.io networks: {default: {aliases: [data3.htz-fsn.prod.ooni.nu, clickhouse3.prod.ooni.io]}} +# The role's config listens on "::" only, as in production. Docker turns IPv6 off +# in containers on an IPv4-only network, and ClickHouse then can't listen at all. +networks: + default: + enable_ipv6: true diff --git a/ansible/roles/oonidata_clickhouse/tests/compose.yml b/ansible/roles/oonidata_clickhouse/tests/compose.yml index 0ed6cd3f..863bbc28 100644 --- a/ansible/roles/oonidata_clickhouse/tests/compose.yml +++ b/ansible/roles/oonidata_clickhouse/tests/compose.yml @@ -31,3 +31,8 @@ services: volumes: ["./out/data4.htz-fsn.prod.ooni.nu:/etc/clickhouse-server:ro,z", "./out:/out:ro,z"] volumes: data3: +# The role's config listens on "::" only, as in production. Docker turns IPv6 off +# in containers on an IPv4-only network, and ClickHouse then can't listen at all. +networks: + default: + enable_ipv6: true From 875aa4717475b582a5acf6657b5563dbea242214 Mon Sep 17 00:00:00 2001 From: Aaron Gibson Date: Wed, 28 Oct 2026 10:08:27 +0000 Subject: [PATCH 16/16] clickhouse role tests: 1.5 GB per ClickHouse container At 700 MB, ClickHouse's server-wide limit (90% of it) failed some of the queries the test runs during the role on GitHub Actions. --- ansible/roles/clickhouse/tests/compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/roles/clickhouse/tests/compose.yml b/ansible/roles/clickhouse/tests/compose.yml index cc889b91..67c76fbc 100644 --- a/ansible/roles/clickhouse/tests/compose.yml +++ b/ansible/roles/clickhouse/tests/compose.yml @@ -8,7 +8,7 @@ x-node: &node - ./out/users.xml:/etc/clickhouse-server/users.xml:ro,z - ./out:/out:ro,z ulimits: {nofile: 262144} - mem_limit: 700m + mem_limit: 1500m # ClickHouse caps itself at 90% of it; at 700m, queries during the role ran out on CI services: fresh: {<<: *node, ports: ["127.0.0.1:18120:8123"]} data1: {<<: *node, ports: ["127.0.0.1:18121:8123"]}