From 4ffb0a2a59f1b721635708eff5efd4406f325ead Mon Sep 17 00:00:00 2001 From: Adam James Date: Tue, 12 Mar 2024 14:55:17 +0000 Subject: [PATCH] Initial commit. --- .github/dependabot.yml | 6 + .github/workflows/ci.yml | 94 +++++++++ .github/workflows/dependabot-auto-merge.yml | 32 ++++ .gitignore | 4 + .yamllint | 33 ++++ LICENSE | 121 ++++++++++++ defaults/main.yml | 116 +++++++++++ files/configuration.xsl | 24 +++ files/logback.xml | 114 +++++++++++ handlers/main.yml | 13 ++ meta/main.yml | 23 +++ molecule/default/converge.yml | 8 + molecule/default/inventory/group_vars/all.yml | 1 + .../group_vars/zookeeper_servers.yml | 3 + .../default/inventory/host_vars/instance2.yml | 3 + .../default/inventory/host_vars/instance3.yml | 4 + molecule/default/molecule.yml | 55 ++++++ molecule/default/prepare.yml | 14 ++ molecule/default/verify.yml | 40 ++++ tasks/main.yml | 181 ++++++++++++++++++ .../etc/systemd/system/zookeeper.service.j2 | 10 + templates/etc/zookeeper/file.j2 | 12 ++ templates/etc/zookeeper/java.env.j2 | 8 + templates/etc/zookeeper/zoo.cfg.j2 | 14 ++ templates/systemd-macros.j2 | 20 ++ templates/zookeeper-macros.j2 | 15 ++ test_plugins/type_tests.py | 13 ++ 27 files changed, 981 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml create mode 100644 .gitignore create mode 100644 .yamllint create mode 100644 LICENSE create mode 100644 defaults/main.yml create mode 100644 files/configuration.xsl create mode 100644 files/logback.xml create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 molecule/default/converge.yml create mode 100644 molecule/default/inventory/group_vars/all.yml create mode 100644 molecule/default/inventory/group_vars/zookeeper_servers.yml create mode 100644 molecule/default/inventory/host_vars/instance2.yml create mode 100644 molecule/default/inventory/host_vars/instance3.yml create mode 100644 molecule/default/molecule.yml create mode 100644 molecule/default/prepare.yml create mode 100644 molecule/default/verify.yml create mode 100644 tasks/main.yml create mode 100644 templates/etc/systemd/system/zookeeper.service.j2 create mode 100644 templates/etc/zookeeper/file.j2 create mode 100644 templates/etc/zookeeper/java.env.j2 create mode 100644 templates/etc/zookeeper/zoo.cfg.j2 create mode 100644 templates/systemd-macros.j2 create mode 100644 templates/zookeeper-macros.j2 create mode 100644 test_plugins/type_tests.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5ace460 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2ffd8f7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +--- +name: CI + +"on": + pull_request: + push: + schedule: + - cron: "11 12 * * 2" + workflow_dispatch: + +defaults: + run: + working-directory: metabrainz.zookeeper + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + path: metabrainz.zookeeper + + - name: Set up Python 3 + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install dependencies + run: pip3 install yamllint ansible ansible-lint + + - name: Run lint + run: | + yamllint . + ansible-lint + + test: + name: Test (py${{ matrix.python }} ansible${{ matrix.ansible }} ${{ matrix.distro }}) + runs-on: ubuntu-latest + needs: + - lint + strategy: + matrix: + python: + - "3.10" + - "3.11" + ansible: + - 7 + - 8 + - 9 + distro: + - ubuntu2004 + - ubuntu2204 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + path: metabrainz.zookeeper + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Generate test-requirements.txt + run: | + cat < test-requirements.txt + ansible==${{ matrix.ansible }}.* + docker==6.* + molecule-plugins[docker] + # https://github.com/docker/docker-py/issues/3113 + urllib3<2.0 + EOF + echo "REQS_HASH=$(sha256sum test-requirements.txt | awk '{ print $1 }')" >> "$GITHUB_ENV" + + - name: Cache pip directory + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-py${{ matrix.python }}-ansible${{ matrix.ansible }}-${{ env.REQS_HASH }} + + - name: Install test dependencies + run: pip3 install -r test-requirements.txt + + - name: Run pip freeze + run: pip3 freeze --all + + - name: Run tests + run: molecule test + env: + ANSIBLE_FORCE_COLOR: 1 + PY_COLORS: 1 + MOLECULE_DISTRO: ${{ matrix.distro }} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..55a82ff --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,32 @@ +name: Auto-merge Dependabot PRs +on: pull_request_target + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Fetch Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@v1.6.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Auto-merge patch and minor version Dependabot PRs + if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }} + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Add comment to major version Dependabot PRs + if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-major' }} + run: | + gh pr comment $PR_URL --body "This PR was not auto-merged because it includes a major version update." + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7edd1e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.tox +.vscode +__pycache__ diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..8827676 --- /dev/null +++ b/.yamllint @@ -0,0 +1,33 @@ +--- +# Based on ansible-lint config +extends: default + +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 + comments-indentation: disable + document-start: disable + empty-lines: + max: 3 + level: error + hyphens: + level: error + indentation: disable + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: disable + new-lines: + type: unix + trailing-spaces: disable + truthy: disable diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..57ca6bd --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,116 @@ +--- +zookeeper_version: 3.9.1 +zookeeper_download_url: >- + https://archive.apache.org/dist/zookeeper/zookeeper-{{ zookeeper_version }}/apache-zookeeper-{{ zookeeper_version }}-bin.tar.gz +zookeeper_download_checksum: >- + sha512:6a1c56557ee8de63dc0730de6c55640afa8ae9043e57539fed393120fe3adfb7f30a6ac13af0a6331ff34ba9c6f2b31e41e40c5446e669651522fffb9ce64e48 + +zookeeper_package_dependencies: + - gzip + - tar + - openjdk-17-jre-headless +zookeeper_package_state: present +# zookeeper_package_cache_valid_time: 600 + +zookeeper_base_directory: /opt/zookeeper +zookeeper_download_directory: "{{ zookeeper_base_directory }}/archives" +zookeeper_release_directory: "{{ zookeeper_base_directory }}/releases" +zookeeper_release_path: "{{ zookeeper_release_directory }}/zookeeper-{{ zookeeper_version }}" +zookeeper_current_release_path: "{{ zookeeper_release_directory }}/current" + +zookeeper_service_enabled: true +# zookeeper_service_state: started +zookeeper_restart_handler_state: restarted + +zookeeper_user: zookeeper +# zookeeper_user_uid: +zookeeper_user_group: zookeeper +# zookeeper_user_group_gid: +zookeeper_user_shell: /usr/sbin/nologin +zookeeper_user_home: /var/lib/zookeeper + +zookeeper_configuration_directory: "/etc/zookeeper" +zookeeper_data_directory: "{{ zookeeper_user_home }}" +zookeeper_data_log_directory: "{{ zookeeper_data_directory }}/log" +zookeeper_log_directory: "/var/log/zookeeper" + +zookeeper_configuration_file: "{{ zookeeper_configuration_directory }}/zoo.cfg" +# zookeeper_configuration_file_owner: +# zookeeper_configuration_file_group: +# zookeeper_configuration_file_mode: + +zookeeper_configuration_files: + configuration.xsl: + file: configuration.xsl + logback.xml: + file: logback.xml +zookeeper_group_configuration_files: {} +zookeeper_host_configuration_files: {} + +zookeeper_environment_file: "{{ zookeeper_configuration_directory }}/java.env" +# zookeeper_environment_file_owner: +# zookeeper_environment_file_group: +# zookeeper_environment_file_mode: + +zookeeper_environment: + ZOO_LOG_DIR: "{{ zookeeper_log_directory }}" +zookeeper_group_environment: {} +zookeeper_host_environment: {} + +zookeeper_configuration: + tickTime: 2000 + initLimit: 10 + syncLimit: 5 + dataDir: "{{ zookeeper_data_directory }}" + dataLogDir: "{{ zookeeper_data_log_directory }}" + clientPort: 2181 + # maxClientCnxns: 60 + autopurge: + snapRetainCount: 3 + purgeInterval: 1 + # metricsProvider: + # className: org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider + # httpHost: 0.0.0.0 + # httpPort: 7000 + # exportJvmInfo: true +zookeeper_group_configuration: {} +zookeeper_host_configuration: {} + +zookeeper_server_inventory_hosts: [] +zookeeper_server_host_fact: "zookeeper_server_configuration" +zookeeper_server_default_ports: "2888:3888" + +zookeeper_service_configuration: + Unit: + Description: Apache ZooKeeper server + Requires: network-online.target + After: network-online.target + Service: + User: "{{ zookeeper_user }}" + Group: "{{ zookeeper_user_group }}" + ExecStart: >- + {{ zookeeper_current_release_path }}/bin/zkServer.sh + --config {{ zookeeper_configuration_directory }} + start-foreground + ExecStop: >- + {{ zookeeper_current_release_path }}/bin/zkServer.sh + --config {{ zookeeper_configuration_directory }} + stop + PrivateDevices: true + PrivateTmp: true + ProtectControlGroups: true + ProtectKernelTunables: true + ProtectSystem: strict + ReadWritePaths: >- + {{ zookeeper_data_directory }} + {{ zookeeper_data_log_directory }} + {{ zookeeper_log_directory }} + Install: + WantedBy: multi-user.target +zookeeper_group_service_configuration: {} +zookeeper_host_service_configuration: {} + +zookeeper_configuration_list_merge: replace +zookeeper_configuration_recursive_merge: true +zookeeper_service_configuration_list_merge: replace +zookeeper_service_configuration_recursive_merge: true diff --git a/files/configuration.xsl b/files/configuration.xsl new file mode 100644 index 0000000..377cdbe --- /dev/null +++ b/files/configuration.xsl @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + +
namevaluedescription
+ + +
+
diff --git a/files/logback.xml b/files/logback.xml new file mode 100644 index 0000000..84a5389 --- /dev/null +++ b/files/logback.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + %d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n + + + ${zookeeper.console.threshold} + + + + + + + + + + + + + + + + diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..a6d5c81 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,13 @@ +--- +- name: Reload SystemD + listen: reload systemd + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart ZooKeeper + listen: restart zookeeper + ansible.builtin.service: + name: zookeeper + state: "{{ zookeeper_restart_handler_state }}" + when: zookeeper_service_enabled + ignore_errors: "{{ ansible_check_mode }}" diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..bb07ede --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,23 @@ +--- +galaxy_info: + role_name: zookeeper + namespace: metabrainz + company: MetaBrainz Foundation + author: Adam James (atj) + description: Installs and configures Apache ZooKeeper + license: CC0 + min_ansible_version: "2.14" + platforms: + - name: Ubuntu + versions: + - focal + - jammy + - name: Debian + version: + - buster + - bullseye + - bookworm + galaxy_tags: + - zookeeper + - debian + - ubuntu diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..424dee7 --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + gather_facts: false + tasks: + - name: "Include metabrainz.zookeeper" + ansible.builtin.include_role: + name: "metabrainz.zookeeper" diff --git a/molecule/default/inventory/group_vars/all.yml b/molecule/default/inventory/group_vars/all.yml new file mode 100644 index 0000000..bcf322a --- /dev/null +++ b/molecule/default/inventory/group_vars/all.yml @@ -0,0 +1 @@ +zookeeper_server_inventory_hosts: "{{ groups['zookeeper_servers'] }}" diff --git a/molecule/default/inventory/group_vars/zookeeper_servers.yml b/molecule/default/inventory/group_vars/zookeeper_servers.yml new file mode 100644 index 0000000..381e6bb --- /dev/null +++ b/molecule/default/inventory/group_vars/zookeeper_servers.yml @@ -0,0 +1,3 @@ +zookeeper_server_configuration: + hostname: "{{ inventory_hostname }}" + id: "{{ groups['zookeeper_servers'].index(inventory_hostname) + 1 }}" diff --git a/molecule/default/inventory/host_vars/instance2.yml b/molecule/default/inventory/host_vars/instance2.yml new file mode 100644 index 0000000..b03a618 --- /dev/null +++ b/molecule/default/inventory/host_vars/instance2.yml @@ -0,0 +1,3 @@ +zookeeper_server_configuration: + hostname: "{{ inventory_hostname }}" + id: "42" diff --git a/molecule/default/inventory/host_vars/instance3.yml b/molecule/default/inventory/host_vars/instance3.yml new file mode 100644 index 0000000..6a0128b --- /dev/null +++ b/molecule/default/inventory/host_vars/instance3.yml @@ -0,0 +1,4 @@ +zookeeper_host_configuration: + metricsProvider: + className: org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider + httpHost: 127.0.0.1 diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..1ff9fe1 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,55 @@ +--- +dependency: + name: galaxy +driver: + name: docker +platforms: + - name: instance1 + image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + networks: + - name: zookeeper + network_mode: zookeeper + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + pre_build_image: true + groups: + - zookeeper_servers + - name: instance2 + image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + networks: + - name: zookeeper + network_mode: zookeeper + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + pre_build_image: true + groups: + - zookeeper_servers + - name: instance3 + image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + networks: + - name: zookeeper + network_mode: zookeeper + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + pre_build_image: true + groups: + - zookeeper_servers +provisioner: + name: ansible + playbooks: + converge: ${MOLECULE_PLAYBOOK:-converge.yml} + inventory: + links: + group_vars: inventory/group_vars/ + host_vars: inventory/host_vars/ +verifier: + name: ansible diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml new file mode 100644 index 0000000..08d3db4 --- /dev/null +++ b/molecule/default/prepare.yml @@ -0,0 +1,14 @@ +--- +- name: Prepare + hosts: all + tasks: + - name: Update apt cache + ansible.builtin.apt: + cache_valid_time: 600 + + # update-alternatives: error: error creating symbolic link '/usr/share/man/man1/java.1.gz.dpkg-tmp': No such file or directory + - name: Create man1 directory for update-alternatives + ansible.builtin.file: + path: /usr/share/man/man1 + mode: "0755" + state: directory diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml new file mode 100644 index 0000000..793a9b5 --- /dev/null +++ b/molecule/default/verify.yml @@ -0,0 +1,40 @@ +--- +- name: Verify + hosts: all + gather_facts: false + tasks: + - name: Ensure ZooKeeper is running + ansible.builtin.service: + name: zookeeper + state: started + register: result + check_mode: true + failed_when: result is changed + + - name: Ensure a cluster leader has been elected + ansible.builtin.uri: + url: http://localhost:8080/commands/leader + return_content: true + register: result + changed_when: false + failed_when: >- + result.status != 200 or + result.json is not defined or + result.json.error is not defined or + result.json.error + + - name: Log ZooKeeper leader command output + ansible.builtin.debug: + var: result.json + +- name: Verify instance3 + hosts: instance3 + gather_facts: false + tasks: + - name: Ensure Prometheus metrics provider is enabled + ansible.builtin.uri: + url: http://localhost:7000/metrics + return_content: true + register: result + changed_when: false + failed_when: result.status != 200 diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..ea83a66 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,181 @@ +--- +- name: Install ZooKeeper dependencies + ansible.builtin.apt: + name: "{{ zookeeper_package_dependencies }}" + state: "{{ zookeeper_package_state }}" + cache_valid_time: "{{ zookeeper_package_cache_valid_time | d(omit) }}" + +- name: Create ZooKeeper group + ansible.builtin.group: + name: "{{ zookeeper_user_group }}" + gid: "{{ zookeeper_user_group_gid | d(omit) }}" + state: present + system: true + +- name: Create ZooKeeper user + ansible.builtin.user: + name: "{{ zookeeper_user }}" + uid: "{{ zookeeper_user_uid | d(omit) }}" + group: "{{ zookeeper_user_group }}" + shell: "{{ zookeeper_user_shell }}" + system: true + create_home: true + home: "{{ zookeeper_user_home }}" + +- name: Create ZooKeeper directories + ansible.builtin.file: + path: "{{ item.path | d(item) }}" + owner: "{{ item.owner | d('root') }}" + group: "{{ item.group | d('root') }}" + mode: "{{ item.mode | d('0755') }}" + state: directory + loop: + - path: "{{ zookeeper_configuration_directory }}" + group: "{{ zookeeper_user_group }}" + mode: "0750" + - path: "{{ zookeeper_data_directory }}" + owner: "{{ zookeeper_user }}" + group: "{{ zookeeper_user_group }}" + mode: "0750" + - path: "{{ zookeeper_data_log_directory }}" + owner: "{{ zookeeper_user }}" + group: "{{ zookeeper_user_group }}" + mode: "0755" + - "{{ zookeeper_download_directory }}" + - path: "{{ zookeeper_log_directory }}" + owner: "{{ zookeeper_user }}" + group: "{{ zookeeper_user_group }}" + - "{{ zookeeper_release_path }}" + +- name: Stat ZooKeeper current release path + ansible.builtin.stat: + path: "{{ zookeeper_current_release_path }}" + get_attributes: false + get_checksum: false + get_mime: false + register: _zookeeper_current_release_path + +- name: Fail if ZooKeeper current release path exists and is not a symlink + ansible.builtin.fail: + msg: "{{ zookeeper_current_release_path }} exists and is not a symlink, aborting..." + when: + - _zookeeper_current_release_path.stat.islnk is defined + - not _zookeeper_current_release_path.stat.islnk + +- name: Download ZooKeeper + ansible.builtin.get_url: + url: "{{ zookeeper_download_url }}" + dest: "{{ zookeeper_download_directory }}/{{ zookeeper_download_url | basename }}" + checksum: "{{ zookeeper_download_checksum | d(omit) }}" + mode: "0644" + register: _zookeeper_download + ignore_errors: "{{ ansible_check_mode }}" + +- name: Unarchive ZooKeeper + ansible.builtin.unarchive: + src: "{{ zookeeper_download_directory }}/{{ zookeeper_download_url | basename }}" + dest: "{{ zookeeper_release_path }}" + mode: "0755" + remote_src: true + extra_opts: + - --strip-components=1 + when: _zookeeper_download is changed # noqa no-handler + +- name: Install ZooKeeper symlink + ansible.builtin.file: + src: "{{ zookeeper_release_path }}" + dest: "{{ zookeeper_current_release_path }}" + state: link + notify: restart zookeeper + +- name: Install ZooKeeper server SystemD service + ansible.builtin.template: + src: etc/systemd/system/zookeeper.service.j2 + dest: /etc/systemd/system/zookeeper.service + mode: "0644" + notify: + - reload systemd + - restart zookeeper + vars: + _service_config: >- + {{ + zookeeper_service_configuration | combine( + zookeeper_group_service_configuration, + zookeeper_host_service_configuration, + list_merge=zookeeper_service_configuration_list_merge, + recursive=zookeeper_service_configuration_recursive_merge + ) + }} + +- name: Install ZooKeeper environment file + ansible.builtin.template: + src: etc/zookeeper/java.env.j2 + dest: "{{ zookeeper_environment_file }}" + owner: "{{ zookeeper_environment_file_owner | d(omit) }}" + group: "{{ zookeeper_environment_file_group | d(omit) }}" + mode: "{{ zookeeper_environment_file_mode | d('0644') }}" + notify: restart zookeeper + vars: + _zookeeper_environment: >- + {{ + zookeeper_environment | combine( + zookeeper_group_environment, + zookeeper_host_environment + ) + }} + +- name: Install ZooKeeper configuration file + ansible.builtin.template: + src: etc/zookeeper/zoo.cfg.j2 + dest: "{{ zookeeper_configuration_file }}" + owner: "{{ zookeeper_configuration_file_owner | d(omit) }}" + group: "{{ zookeeper_configuration_file_group | d(omit) }}" + mode: "{{ zookeeper_configuration_file_mode | d('0644') }}" + notify: restart zookeeper + vars: + _zookeeper_configuration: >- + {{ + zookeeper_configuration | combine( + zookeeper_group_configuration, + zookeeper_host_configuration, + list_merge=zookeeper_configuration_list_merge, + recursive=zookeeper_configuration_recursive_merge + ) + }} + +- name: Install additional ZooKeeper configuration files + ansible.builtin.template: + src: "etc/zookeeper/file.j2" + dest: "{{ zookeeper_configuration_directory }}/{{ item.key }}" + owner: "{{ item.value.owner | d(omit) }}" + group: "{{ item.value.group | d(omit) }}" + mode: "{{ item.value.mode | d('0644') }}" + backup: "{{ item.value.backup | d(omit) }}" + loop: "{{ _zookeeper_configuration_files | dict2items }}" + notify: restart zookeeper + vars: + _zookeeper_configuration_files: >- + {{ + zookeeper_configuration_files | combine( + zookeeper_group_configuration_files, + zookeeper_host_configuration_files, + recursive=zookeeper_configuration_recursive_merge + ) + }} + +- name: Install ZooKeeper cluster ID file + ansible.builtin.copy: + dest: "{{ zookeeper_data_directory }}/myid" + content: "{{ zookeeper_server_configuration.id }}" + owner: "{{ zookeeper_user }}" + group: "{{ zookeeper_user_group }}" + mode: "0644" + notify: restart zookeeper + when: zookeeper_server_inventory_hosts | d([]) | length > 0 + +- name: Enable ZooKeeper service + ansible.builtin.service: + name: zookeeper + enabled: "{{ zookeeper_service_enabled }}" + state: "{{ zookeeper_service_state | d(omit) }}" + ignore_errors: "{{ ansible_check_mode }}" diff --git a/templates/etc/systemd/system/zookeeper.service.j2 b/templates/etc/systemd/system/zookeeper.service.j2 new file mode 100644 index 0000000..a276617 --- /dev/null +++ b/templates/etc/systemd/system/zookeeper.service.j2 @@ -0,0 +1,10 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True +# {{ ansible_managed }} +{% import "systemd-macros.j2" as macros with context %} + +{% for section, config in (_service_config | dictsort) %} +{{ macros.systemd_section(config, section) -}} +{% if not loop.last %} + +{% endif %} +{% endfor %} diff --git a/templates/etc/zookeeper/file.j2 b/templates/etc/zookeeper/file.j2 new file mode 100644 index 0000000..91c1834 --- /dev/null +++ b/templates/etc/zookeeper/file.j2 @@ -0,0 +1,12 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True +{% if item.value is mapping -%} +{% if item.value.content is defined -%} +{{ item.value.content }} +{% elif item.value.template is defined %} +{{ lookup('ansible.builtin.template', item.value.template) }} +{% else %} +{{ lookup('ansible.builtin.file', item.value.file | mandatory('content, template or file must be specified')) }} +{%- endif %} +{% else %} +{{ item.value | string }} +{%- endif %} diff --git a/templates/etc/zookeeper/java.env.j2 b/templates/etc/zookeeper/java.env.j2 new file mode 100644 index 0000000..75b2e51 --- /dev/null +++ b/templates/etc/zookeeper/java.env.j2 @@ -0,0 +1,8 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True +# {{ ansible_managed }} +{% if _zookeeper_environment %} + +{% for key, value in (_zookeeper_environment | dictsort) %} +{{ key }}={{ value | quote }} +{% endfor %} +{% endif %} diff --git a/templates/etc/zookeeper/zoo.cfg.j2 b/templates/etc/zookeeper/zoo.cfg.j2 new file mode 100644 index 0000000..1d3126a --- /dev/null +++ b/templates/etc/zookeeper/zoo.cfg.j2 @@ -0,0 +1,14 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True +# {{ ansible_managed }} +{% import "zookeeper-macros.j2" as macros without context %} + +{% for option, value in (_zookeeper_configuration | dictsort) %} +{{ macros.zookeeper_format_option(option, value) -}} +{% endfor %} +{% if (zookeeper_server_inventory_hosts | d([])) | length > 1 %} + +{% for host in zookeeper_server_inventory_hosts %} +{% set _server = hostvars[host][zookeeper_server_host_fact] %} +server.{{ _server.id | mandatory }}={{ _server.hostname | mandatory }}:{{ _server.ports | d(zookeeper_server_default_ports) }} +{% endfor %} +{% endif %} diff --git a/templates/systemd-macros.j2 b/templates/systemd-macros.j2 new file mode 100644 index 0000000..ae85aa3 --- /dev/null +++ b/templates/systemd-macros.j2 @@ -0,0 +1,20 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True + +{% macro systemd_section(config, section) -%} +[{{ section }}] +{% for option, value in config|dictsort %} +{{ systemd_format_option(option, value) -}} +{% endfor %} +{% endmacro -%} + +{% macro systemd_format_option(option, value) -%} +{% if value is bool %} +{{ option }}={{ value | string | lower }} +{% elif value is list and value %} +{% for v in value %} +{{ option }}={{ (v | string | lower) if v is bool else (v | string) }} +{% endfor %} +{% else %} +{{ option }}={{ value | string }} +{% endif %} +{% endmacro -%} diff --git a/templates/zookeeper-macros.j2 b/templates/zookeeper-macros.j2 new file mode 100644 index 0000000..beed57d --- /dev/null +++ b/templates/zookeeper-macros.j2 @@ -0,0 +1,15 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True + +{% macro zookeeper_format_option(option, value) -%} +{% if value is bool %} +{{ option }}={{ value | string | lower }} +{% elif value is list %} +{{ option }}={{ v | join(',') }} +{% elif value is mapping %} +{% for o, v in (value | dictsort) %} +{{ option }}.{{ zookeeper_format_option(o, v) -}} +{% endfor %} +{% else %} +{{ option }}={{ value | string }} +{% endif %} +{% endmacro -%} diff --git a/test_plugins/type_tests.py b/test_plugins/type_tests.py new file mode 100644 index 0000000..3e40178 --- /dev/null +++ b/test_plugins/type_tests.py @@ -0,0 +1,13 @@ +def test_bool(value): + return isinstance(value, bool) + +def test_list(value): + return isinstance(value, list) + +class TestModule(object): + + def tests(self): + return { + 'bool': test_bool, + 'list': test_list + }