├── .ansible-lint ├── .config └── molecule │ └── config.yml ├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── alloy-molecule.yml │ ├── full-integration-test.yml │ ├── lint.yaml │ ├── loki-molecule.yml │ ├── mimir-molecule.yml │ ├── modules-test.yml │ ├── opentelemetry-collector-molecule.yml │ ├── promtail-molecule.yml │ ├── release.yml │ └── roles-test.yml ├── .gitignore ├── .markdownlint.yaml ├── .shellcheckrc ├── .textlintrc ├── .yamllint ├── CHANGELOG.rst ├── CODEOWNERS ├── LICENSE ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── ansible.cfg ├── catalog-info.yaml ├── changelogs ├── .plugin-cache.yaml ├── changelog.yaml └── config.yaml ├── examples ├── agent-basic-no-options.yaml ├── agent-mode-flow-with-dynamic-conf.yaml ├── agent-send-to-grafana-cloud.yaml ├── alloy.yaml ├── ansible.cfg ├── loki-basic-no-options.yml ├── loki-local-filesystem-with-retention-and-alert.yml ├── mimir-3-hosts.yaml ├── monitor-multiple-instance-otel.md ├── monitor-multiple-instances-agent.md ├── opentelemetry-collector.yml └── promtail-multiple-logs.yml ├── galaxy.yml ├── meta └── runtime.yml ├── package.json ├── plugins └── modules │ ├── alert_contact_point.py │ ├── alert_notification_policy.py │ ├── cloud_api_key.py │ ├── cloud_plugin.py │ ├── cloud_stack.py │ ├── dashboard.py │ ├── datasource.py │ ├── folder.py │ └── user.py ├── requirements.txt ├── requirements.yml ├── roles ├── alloy │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── tasks │ │ ├── deploy.yml │ │ ├── main.yml │ │ ├── preflight.yml │ │ ├── setup-Debian.yml │ │ ├── setup-RedHat.yml │ │ └── uninstall.yml │ ├── templates │ │ ├── alloy.j2 │ │ ├── config.alloy.j2 │ │ └── override.conf.j2 │ └── vars │ │ ├── Debian.yml │ │ ├── RedHat.yml │ │ └── main.yml ├── grafana │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── molecule │ │ ├── alternative │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ │ └── test_alternative.py │ │ └── default │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ └── test_default.py │ ├── tasks │ │ ├── api_keys.yml │ │ ├── configure.yml │ │ ├── dashboards.yml │ │ ├── datasources.yml │ │ ├── install.yml │ │ ├── main.yml │ │ ├── notifications.yml │ │ ├── plugins.yml │ │ └── preflight.yml │ ├── templates │ │ ├── grafana.ini.j2 │ │ ├── ldap.toml.j2 │ │ └── tmpfiles.j2 │ ├── test-requirements.txt │ └── vars │ │ └── distro │ │ ├── debian.yml │ │ ├── redhat.yml │ │ └── suse.yml ├── grafana_agent │ ├── README.md │ ├── defaults │ │ └── main.yaml │ ├── handlers │ │ └── main.yaml │ ├── meta │ │ └── main.yaml │ ├── tasks │ │ ├── configure.yaml │ │ ├── ga-started.yaml │ │ ├── install.yaml │ │ ├── install │ │ │ ├── directories.yaml │ │ │ ├── download-install.yaml │ │ │ ├── local-install.yaml │ │ │ └── user-group.yaml │ │ ├── main.yaml │ │ ├── preflight.yaml │ │ └── preflight │ │ │ ├── download.yaml │ │ │ ├── install.yaml │ │ │ ├── systemd.yaml │ │ │ └── vars.yaml │ ├── templates │ │ ├── EnvironmentFile.j2 │ │ ├── config.yaml.j2 │ │ └── grafana-agent.service.j2 │ └── vars │ │ └── main.yaml ├── loki │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── tasks │ │ ├── deploy.yml │ │ ├── main.yml │ │ ├── setup-Debian.yml │ │ ├── setup-RedHat.yml │ │ └── uninstall.yml │ ├── templates │ │ ├── config.yml.j2 │ │ └── rules.yml.j2 │ └── vars │ │ ├── Debian.yml │ │ └── RedHat.yml ├── mimir │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── files │ │ └── .gitkeep │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ └── test_default.py │ ├── tasks │ │ ├── deploy.yml │ │ ├── main.yml │ │ ├── setup-Debian.yml │ │ ├── setup-Redhat.yml │ │ └── uninstall.yml │ ├── templates │ │ ├── .gitkeep │ │ └── config.yml.j2 │ └── vars │ │ └── .gitkeep ├── opentelemetry_collector │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── molecule │ │ ├── default-check-first │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ │ └── test_default.py │ │ ├── default │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ │ └── test_default.py │ │ ├── latest │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ │ └── test_default.py │ │ └── non-contrib │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ └── test_default.py │ ├── tasks │ │ ├── configure.yml │ │ ├── install.yml │ │ ├── main.yml │ │ └── service.yml │ └── templates │ │ ├── otel_collector.service.j2 │ │ └── otel_collector_config.yml.j2 ├── promtail │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── tasks │ │ ├── acl_configuration.yml │ │ ├── deploy.yml │ │ ├── main.yml │ │ ├── setup-Debian.yml │ │ ├── setup-RedHat.yml │ │ └── uninstall.yml │ ├── templates │ │ ├── config.yml.j2 │ │ ├── promtail.service.j2 │ │ └── promtail_acl.j2 │ └── vars │ │ ├── Debian.yml │ │ └── RedHat.yml └── tempo │ ├── README.md │ ├── defaults │ └── main.yml │ ├── handlers │ └── main.yml │ ├── meta │ └── main.yml │ ├── molecule │ └── default │ │ ├── converge.yml │ │ └── molecule.yml │ ├── tasks │ ├── deploy.yml │ ├── main.yml │ ├── setup-Debian.yml │ ├── setup-Redhat.yml │ └── uninstall.yml │ ├── templates │ └── config.yml.j2 │ └── vars │ └── main.yml ├── tests └── integration │ ├── requirements.txt │ └── targets │ ├── alert_contact_point │ └── tasks │ │ └── main.yml │ ├── alert_notification_policy │ └── tasks │ │ └── main.yml │ ├── cloud_api_key │ └── tasks │ │ └── main.yml │ ├── cloud_plugin │ └── tasks │ │ └── main.yml │ ├── create_cloud_stack │ └── tasks │ │ └── main.yml │ ├── dashboard │ └── tasks │ │ └── main.yml │ ├── datasource │ └── tasks │ │ └── main.yml │ ├── delete_cloud_stack │ └── tasks │ │ └── main.yml │ ├── folder │ └── tasks │ │ └── main.yml │ ├── molecule-grafana-alternative │ └── runme.sh │ ├── molecule-grafana-default │ └── runme.sh │ └── user │ └── tasks │ └── main.yml ├── tools ├── includes │ ├── logging.sh │ └── utils.sh ├── lint-ansible.sh ├── lint-editorconfig.sh ├── lint-markdown.sh ├── lint-shell.sh ├── lint-text.sh ├── lint-yaml.sh └── setup.sh └── yarn.lock /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | profile: production 3 | 4 | exclude_paths: 5 | - .cache/ 6 | - .github/ 7 | - examples/ 8 | 9 | parseable: true 10 | verbosity: 1 11 | 12 | use_default_rules: true 13 | 14 | skip_list: 15 | - '204' # Allow string length greater than 160 chars 16 | - 'no-changed-when' # False positives for running command shells 17 | - 'yaml' # Disable YAML linting since it's done by yamllint 18 | - 'empty-string-compare' # Allow compare to empty string 19 | -------------------------------------------------------------------------------- /.config/molecule/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: docker 6 | platforms: 7 | - name: almalinux-8 8 | image: dokken/almalinux-8 9 | pre_build_image: true 10 | privileged: true 11 | cgroup_parent: docker.slice 12 | command: /lib/systemd/systemd 13 | - name: almalinux-9 14 | image: dokken/almalinux-9 15 | pre_build_image: true 16 | privileged: true 17 | cgroup_parent: docker.slice 18 | command: /lib/systemd/systemd 19 | - name: centos-7 20 | image: dokken/centos-7 21 | pre_build_image: true 22 | privileged: true 23 | cgroup_parent: docker.slice 24 | command: /usr/lib/systemd/systemd 25 | - name: centos-stream-8 26 | image: dokken/centos-stream-8 27 | pre_build_image: true 28 | privileged: true 29 | cgroup_parent: docker.slice 30 | command: /lib/systemd/systemd 31 | - name: centos-stream-9 32 | image: dokken/centos-stream-9 33 | pre_build_image: true 34 | privileged: true 35 | cgroup_parent: docker.slice 36 | command: /lib/systemd/systemd 37 | - name: debian-10 38 | image: dokken/debian-10 39 | pre_build_image: true 40 | privileged: true 41 | cgroup_parent: docker.slice 42 | command: /lib/systemd/systemd 43 | - name: debian-11 44 | image: dokken/debian-11 45 | pre_build_image: true 46 | privileged: true 47 | cgroup_parent: docker.slice 48 | command: /lib/systemd/systemd 49 | - name: fedora-36 50 | image: dokken/fedora-36 51 | pre_build_image: true 52 | privileged: true 53 | cgroup_parent: docker.slice 54 | command: /lib/systemd/systemd 55 | - name: fedora-37 56 | image: dokken/fedora-37 57 | pre_build_image: true 58 | privileged: true 59 | cgroup_parent: docker.slice 60 | command: /lib/systemd/systemd 61 | - name: ubuntu-18.04 62 | image: dokken/ubuntu-18.04 63 | pre_build_image: true 64 | privileged: true 65 | cgroup_parent: docker.slice 66 | command: /lib/systemd/systemd 67 | - name: ubuntu-20.04 68 | image: dokken/ubuntu-20.04 69 | pre_build_image: true 70 | privileged: true 71 | cgroup_parent: docker.slice 72 | command: /lib/systemd/systemd 73 | - name: ubuntu-22.04 74 | image: dokken/ubuntu-22.04 75 | pre_build_image: true 76 | privileged: true 77 | cgroup_parent: docker.slice 78 | command: /lib/systemd/systemd 79 | provisioner: 80 | env: 81 | ANSIBLE_INJECT_FACT_VARS: false 82 | verifier: 83 | name: testinfra 84 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is the top-most EditorConfig file 2 | root = true 3 | 4 | # All Files 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # JSON Files 14 | [*.{json,json5,webmanifest}] 15 | indent_size = 2 16 | 17 | # YAML Files 18 | [*.{yml,yaml}] 19 | indent_size = 2 20 | 21 | # Markdown Files 22 | [*.{md,mdx}] 23 | indent_size = 2 24 | trim_trailing_whitespace = false 25 | 26 | # Web Files 27 | [*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}] 28 | indent_size = 2 29 | 30 | # Bash Files 31 | [*.sh] 32 | indent_size = 2 33 | end_of_line = lf 34 | 35 | # Makefiles 36 | [{Makefile,**.mk}] 37 | indent_style = tab 38 | 39 | # Python Files 40 | [*.py] 41 | indent_style = space 42 | indent_size = 4 43 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/alloy-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Alloy Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | defaults: 13 | run: 14 | working-directory: roles/alloy 15 | 16 | jobs: 17 | molecule: 18 | name: Molecule 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | distro: 23 | - rockylinux9 24 | - ubuntu2204 25 | - debian12 26 | 27 | steps: 28 | - name: Check out the codebase. 29 | uses: actions/checkout@v4 30 | 31 | - name: Set up Python 3. 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: '3.x' 35 | 36 | - name: Install test dependencies. 37 | run: pip3 install ansible molecule molecule-plugins[docker] docker 38 | 39 | - name: Run Molecule tests. 40 | run: molecule test 41 | env: 42 | PY_COLORS: '1' 43 | ANSIBLE_FORCE_COLOR: '1' 44 | MOLECULE_DISTRO: ${{ matrix.distro }} 45 | -------------------------------------------------------------------------------- /.github/workflows/full-integration-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Full Integration Test 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | schedule: 7 | - cron: '0 0 * * 0' 8 | workflow_dispatch: 9 | env: 10 | NAMESPACE: grafana 11 | COLLECTION_NAME: grafana 12 | 13 | jobs: 14 | 15 | integration: 16 | runs-on: ubuntu-20.04 17 | name: ${{ matrix.ansible }}-py${{ matrix.python }} 18 | strategy: 19 | fail-fast: true 20 | max-parallel: 1 21 | matrix: 22 | ansible: 23 | - stable-2.11 24 | - stable-2.12 25 | - stable-2.13 26 | - devel 27 | python: 28 | - '2.7' 29 | - '3.5' 30 | - '3.6' 31 | - '3.7' 32 | - '3.8' 33 | - '3.9' 34 | - '3.10' 35 | exclude: 36 | - ansible: stable-2.11 37 | python: '3.10' 38 | 39 | steps: 40 | - name: Check out code 41 | uses: actions/checkout@v2 42 | with: 43 | path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 44 | 45 | - name: create integration_config 46 | working-directory: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}}/tests/integration 47 | run: | 48 | cat < integration_config.yml 49 | stack_name: ${{ secrets.ANSIBLE_TEST_STACK_NAME }} 50 | org_name: ${{ secrets.ANSIBLE_TEST_ORG_NAME }} 51 | grafana_cloud_api_key: ${{ secrets.ANSIBLE_TEST_CLOUD_API_KEY }} 52 | grafana_api_key: ${{ secrets.ANSIBLE_TEST_GRAFANA_API_KEY }} 53 | test_stack_name: ${{ secrets.ANSIBLE_TEST_CI_STACK }} 54 | EOF 55 | 56 | - name: Set up Python 57 | uses: actions/setup-python@v2 58 | with: 59 | python-version: '3.10' 60 | 61 | - name: Install ansible-base (${{ matrix.ansible }}) 62 | run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check 63 | 64 | - name: Test Modules 65 | run: ansible-test integration -v alert_contact_point alert_notification_policy cloud_api_key cloud_plugin cloud_stack dashboard datasource folder --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --coverage --docker 66 | working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 67 | 68 | - name: Cooling Period 69 | run: sleep 3m 70 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: ["main"] 8 | pull_request: 9 | branches: ["main"] 10 | 11 | jobs: 12 | lint: 13 | name: Perform Linting 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Install shellcheck 17 | run: | 18 | wget -c https://github.com/koalaman/shellcheck/releases/download/v0.9.0/shellcheck-v0.9.0.linux.x86_64.tar.xz -O shellcheck.tar.xz && \ 19 | tar -xvf shellcheck.tar.xz && \ 20 | sudo mv ./shellcheck-v0.9.0/shellcheck /usr/bin/shellcheck && \ 21 | rm -rf shellcheck-v0.9.0 22 | 23 | - uses: actions/checkout@v3 24 | 25 | - uses: actions/setup-node@v3 26 | with: 27 | node-version: 18 28 | cache: npm 29 | 30 | - uses: actions/setup-python@v4 31 | with: 32 | python-version: '3.10' 33 | 34 | - name: Install pipenv 35 | run: | 36 | python -m pip install --upgrade pipenv wheel 37 | 38 | - name: Install dependencies 39 | run: make install 40 | 41 | - name: Shell Linting 42 | run: make ci-lint-shell 43 | if: success() || failure() 44 | 45 | # - name: Markdown Linting 46 | # run: make ci-lint-markdown 47 | # if: success() || failure() 48 | 49 | # - name: Text Linting 50 | # run: make ci-lint-text 51 | # if: success() || failure() 52 | 53 | - name: Yaml Linting 54 | run: make ci-lint-yaml 55 | if: success() || failure() 56 | 57 | - name: Editorconfig Linting 58 | run: make ci-lint-editorconfig 59 | if: success() || failure() 60 | 61 | - name: Ansible Linting 62 | run: make ci-lint-ansible 63 | if: success() || failure() 64 | -------------------------------------------------------------------------------- /.github/workflows/loki-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Loki Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | defaults: 13 | run: 14 | working-directory: roles/loki 15 | 16 | jobs: 17 | molecule: 18 | name: Molecule 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | distro: 23 | - rockylinux9 24 | - ubuntu2204 25 | - debian12 26 | 27 | steps: 28 | - name: Check out the codebase. 29 | uses: actions/checkout@v4 30 | 31 | - name: Set up Python 3. 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: '3.x' 35 | 36 | - name: Install test dependencies. 37 | run: pip3 install ansible molecule molecule-plugins[docker] docker 38 | 39 | - name: Run Molecule tests. 40 | run: molecule test 41 | env: 42 | PY_COLORS: '1' 43 | ANSIBLE_FORCE_COLOR: '1' 44 | MOLECULE_DISTRO: ${{ matrix.distro }} 45 | -------------------------------------------------------------------------------- /.github/workflows/mimir-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mimir Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | defaults: 13 | run: 14 | working-directory: roles/mimir 15 | 16 | jobs: 17 | molecule: 18 | name: Molecule 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | distro: 23 | - rockylinux9 24 | - rockylinux8 25 | - ubuntu2204 26 | - debian12 27 | 28 | steps: 29 | - name: Check out the codebase. 30 | uses: actions/checkout@v4 31 | 32 | - name: Set up Python 3. 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: '3.x' 36 | 37 | - name: Install test dependencies. 38 | run: pip3 install ansible-core==2.16 'molecule-plugins[docker]' pytest-testinfra jmespath selinux passlib 39 | 40 | - name: create docker network 41 | run: docker network create molecule 42 | 43 | - name: Start s3 backend 44 | run: docker run -d -p 9000:9000 -p 9001:9001 --name minio-mimir --network molecule -e "MINIO_ROOT_USER=testtest" -e "MINIO_ROOT_PASSWORD=testtest" -e "MINIO_DEFAULT_BUCKETS=mimir" bitnami/minio:latest 45 | 46 | - name: Run Molecule tests. 47 | run: molecule test 48 | env: 49 | PY_COLORS: '1' 50 | ANSIBLE_FORCE_COLOR: '1' 51 | MOLECULE_DISTRO: ${{ matrix.distro }} 52 | -------------------------------------------------------------------------------- /.github/workflows/modules-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Modules Test 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: 8 | - "main" 9 | pull_request: 10 | schedule: 11 | - cron: '0 6 * * *' 12 | env: 13 | NAMESPACE: grafana 14 | COLLECTION_NAME: grafana 15 | 16 | jobs: 17 | 18 | sanity: 19 | name: Sanity (Ⓐ${{ matrix.ansible }}) 20 | strategy: 21 | matrix: 22 | ansible: 23 | - stable-2.12 24 | - stable-2.13 25 | - stable-2.14 26 | - devel 27 | runs-on: ubuntu-20.04 28 | steps: 29 | 30 | - name: Check out code 31 | uses: actions/checkout@v3 32 | with: 33 | path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 34 | 35 | - name: Set up Python 36 | uses: actions/setup-python@v3 37 | with: 38 | python-version: '3.10' 39 | 40 | - name: Install ansible-base (${{ matrix.ansible }}) 41 | run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check 42 | 43 | - name: Run sanity tests 44 | run: ansible-test sanity -v --docker --color --coverage 45 | working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 46 | 47 | integration: 48 | runs-on: ubuntu-20.04 49 | name: Integration (Ⓐ${{ matrix.ansible }}-py${{ matrix.python }}) 50 | strategy: 51 | fail-fast: true 52 | max-parallel: 1 53 | matrix: 54 | ansible: 55 | - stable-2.13 56 | python: 57 | - '3.10' 58 | 59 | steps: 60 | - name: Check out code 61 | uses: actions/checkout@v2 62 | with: 63 | path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 64 | 65 | - name: create integration_config 66 | working-directory: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}}/tests/integration 67 | run: | 68 | cat < integration_config.yml 69 | org_name: ${{ secrets.ANSIBLE_TEST_ORG_NAME }} 70 | grafana_cloud_api_key: ${{ secrets.ANSIBLE_TEST_CLOUD_API_KEY }} 71 | grafana_api_key: ${{ secrets.ANSIBLE_TEST_GRAFANA_API_KEY }} 72 | grafana_url: ${{ secrets.ANSIBLE_GRAFANA_URL }} 73 | test_stack_name: ${{ secrets.ANSIBLE_TEST_CI_STACK }} 74 | EOF 75 | 76 | - name: Set up Python 77 | uses: actions/setup-python@v2 78 | with: 79 | python-version: ${{ matrix.python }} 80 | 81 | - name: Install ansible-base (${{ matrix.ansible }}) 82 | run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check 83 | 84 | - name: Install Requests 85 | run: pip install requests 86 | 87 | - name: Create Test Stack 88 | run: ansible-test integration -v create_cloud_stack --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --coverage --docker 89 | working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 90 | 91 | - name: Test Modules 92 | run: ansible-test integration -v alert_contact_point alert_notification_policy cloud_api_key cloud_plugin dashboard datasource folder --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --coverage --docker 93 | working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 94 | 95 | - name: Delete Test Stack 96 | if: success() || failure() 97 | run: ansible-test integration -v delete_cloud_stack --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --coverage --docker 98 | working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 99 | -------------------------------------------------------------------------------- /.github/workflows/opentelemetry-collector-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: OpenTelemetry Collector Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | defaults: 13 | run: 14 | working-directory: roles/opentelemetry_collector 15 | 16 | jobs: 17 | molecule: 18 | name: Molecule 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | scenario: 23 | - default 24 | - default-check-first 25 | - latest 26 | - non-contrib 27 | 28 | steps: 29 | - name: Check out the codebase. 30 | uses: actions/checkout@v4 31 | 32 | - name: Set up Python 3. 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: '3.x' 36 | 37 | - name: Install test dependencies. 38 | run: pip3 install ansible molecule molecule-plugins[docker] docker pytest-testinfra 39 | 40 | - name: Run Molecule tests. 41 | run: molecule test -s ${{ matrix.scenario }} 42 | env: 43 | PY_COLORS: '1' 44 | ANSIBLE_FORCE_COLOR: '1' 45 | -------------------------------------------------------------------------------- /.github/workflows/promtail-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Promtail Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | defaults: 13 | run: 14 | working-directory: roles/promtail 15 | 16 | jobs: 17 | molecule: 18 | name: Molecule 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | distro: 23 | - rockylinux9 24 | - ubuntu2204 25 | - debian12 26 | 27 | steps: 28 | - name: Check out the codebase. 29 | uses: actions/checkout@v4 30 | 31 | - name: Set up Python 3. 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: '3.x' 35 | 36 | - name: Install test dependencies. 37 | run: pip3 install ansible molecule molecule-plugins[docker] docker 38 | 39 | - name: Run Molecule tests. 40 | run: molecule test 41 | env: 42 | PY_COLORS: '1' 43 | ANSIBLE_FORCE_COLOR: '1' 44 | MOLECULE_DISTRO: ${{ matrix.distro }} 45 | -------------------------------------------------------------------------------- /.github/workflows/roles-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Roles Test 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: 8 | - "main" 9 | pull_request: 10 | schedule: 11 | - cron: '0 6 * * *' 12 | env: 13 | NAMESPACE: grafana 14 | COLLECTION_NAME: grafana 15 | 16 | jobs: 17 | 18 | sanity: 19 | name: Sanity (Ⓐ${{ matrix.ansible }}) 20 | strategy: 21 | matrix: 22 | ansible: 23 | - stable-2.12 24 | - stable-2.13 25 | - stable-2.14 26 | - devel 27 | runs-on: ubuntu-20.04 28 | steps: 29 | 30 | - name: Check out code 31 | uses: actions/checkout@v3 32 | with: 33 | path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 34 | 35 | - name: Set up Python 36 | uses: actions/setup-python@v3 37 | with: 38 | python-version: '3.10' 39 | 40 | - name: Install ansible-base (${{ matrix.ansible }}) 41 | run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check 42 | 43 | - name: Run sanity tests 44 | run: ansible-test sanity -v --docker --color --coverage 45 | working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 46 | 47 | integration: 48 | runs-on: ubuntu-20.04 49 | name: Integration (Ⓐ${{ matrix.ansible }}-py${{ matrix.python }}) 50 | strategy: 51 | fail-fast: true 52 | max-parallel: 1 53 | matrix: 54 | ansible: 55 | - stable-2.13 56 | python: 57 | - '3.10' 58 | 59 | steps: 60 | - name: Check out code 61 | uses: actions/checkout@v2 62 | with: 63 | path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 64 | 65 | - name: create integration_config 66 | working-directory: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}}/tests/integration 67 | run: | 68 | cat < integration_config.yml 69 | stack_name: ${{ secrets.ANSIBLE_TEST_STACK_NAME }} 70 | org_name: ${{ secrets.ANSIBLE_TEST_ORG_NAME }} 71 | grafana_cloud_api_key: ${{ secrets.ANSIBLE_TEST_CLOUD_API_KEY }} 72 | grafana_api_key: ${{ secrets.ANSIBLE_TEST_GRAFANA_API_KEY }} 73 | grafana_url: ${{ secrets.ANSIBLE_GRAFANA_URL }} 74 | test_stack_name: ${{ secrets.ANSIBLE_TEST_CI_STACK }} 75 | EOF 76 | 77 | - name: Set up Python 78 | uses: actions/setup-python@v2 79 | with: 80 | python-version: ${{ matrix.python }} 81 | 82 | - name: Install ansible-base (${{ matrix.ansible }}) 83 | run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check 84 | 85 | - name: Install Requests 86 | run: pip install requests 87 | 88 | - name: Test Roles 89 | run: ansible-test integration -v molecule-grafana-alternative molecule-grafana-default --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --coverage --docker 90 | working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} 91 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | # Allow opening any 'source'd file, even if not specified as input 2 | external-sources=true 3 | 4 | # some files are sourced which can make some areas appear unreachable 5 | disable=SC2317 6 | disable=SC2250 7 | disable=SC2312 8 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | yaml-files: 3 | - "*.yaml" 4 | - "*.yml" 5 | - ".yamllint" 6 | 7 | ignore: 8 | - node_modules 9 | 10 | extends: default 11 | 12 | rules: 13 | line-length: 14 | max: 150 15 | level: warning 16 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ./ @ishanjainn 2 | /roles/grafana @gardar @ishanjainn 3 | /roles/grafana_agent @ishanjainn @v-zhuravlev @gardar 4 | /roles/alloy @ishanjainn @v-zhuravlev @gardar @voidquark 5 | /roles/opentelemetry_collector @ishanjainn 6 | /roles/loki @voidquark @ishanjainn 7 | /roles/mimir @GVengelen @gardar @ishanjainn 8 | /roles/promtail @voidquark @ishanjainn 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL:= lint 2 | PATH := ./node_modules/.bin:$(PATH) 3 | SHELL := /bin/bash 4 | args = $(filter-out $@, $(MAKECMDGOALS)) 5 | .PHONY: all setup install clean reinstall build compile pdfs lint lint-sh lint-shell lint-md lint-markdown lint-txt lint-text pdf lint-yaml lint-yml lint-editorconfig lint-ec ci-lint ci-lint-shell ci-lint-markdown ci-lint-text ci-lint-yaml ci-lint-editorconfig lint-ansible ci-lint-ansible 6 | 7 | default: all 8 | 9 | all: install 10 | 11 | #################################################################### 12 | # Installation / Setup # 13 | #################################################################### 14 | setup: 15 | @./tools/setup.sh 16 | 17 | install: 18 | yarn install 19 | pipenv install 20 | 21 | # remove the build and log folders 22 | clean: 23 | rm -rf build node_modules 24 | 25 | # reinstall the node_modules and start with a fresh node build 26 | reinstall: clean install 27 | 28 | #################################################################### 29 | # Linting # 30 | #################################################################### 31 | lint: lint-shell lint-markdown lint-text lint-yaml lint-editorconfig lint-ansible 32 | 33 | # Note "|| true" is added to locally make lint can be ran and all linting is preformed, regardless of exit code 34 | 35 | # Shell Linting 36 | lint-sh lint-shell: 37 | @./tools/lint-shell.sh || true 38 | 39 | # Markdown Linting 40 | lint-md lint-markdown: 41 | @./tools/lint-markdown.sh || true 42 | 43 | # Text Linting 44 | lint-txt lint-text: 45 | @./tools/lint-text.sh || true 46 | 47 | # Yaml Linting 48 | lint-yml lint-yaml: 49 | @./tools/lint-yaml.sh || true 50 | 51 | # Editorconfig Linting 52 | lint-ec lint-editorconfig: 53 | @./tools/lint-editorconfig.sh || true 54 | 55 | # Ansible Linting 56 | lint-ansible: 57 | @./tools/lint-ansible.sh || true 58 | 59 | #################################################################### 60 | # CI # 61 | #################################################################### 62 | ci-lint: ci-lint-shell ci-lint-markdown ci-lint-text ci-lint-yaml ci-lint-editorconfig ci-lint-ansible 63 | 64 | # Shell Linting 65 | ci-lint-shell: 66 | @./tools/lint-shell.sh 67 | 68 | # Markdown Linting 69 | ci-lint-markdown: 70 | @./tools/lint-markdown.sh 71 | 72 | # Text Linting 73 | ci-lint-text: 74 | @./tools/lint-text.sh 75 | 76 | # Yaml Linting 77 | ci-lint-yaml: 78 | @./tools/lint-yaml.sh 79 | 80 | # Editorconfig Linting 81 | ci-lint-editorconfig: 82 | @./tools/lint-editorconfig.sh 83 | 84 | # Ansible Linting 85 | ci-lint-ansible: 86 | @./tools/lint-ansible.sh 87 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | yamllint = "==1.35.1" 8 | ansible-lint = ">=6.13.1,<25.0.0" 9 | pylint = ">=2.16.2,<4.0.0" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = "3.10" 15 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | collections_paths = ./ 3 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: grafana-ansible-collection 5 | title: grafana-ansible-collection 6 | description: | 7 | This collection (grafana.grafana) contains modules and roles to assist in automating the management of resources in Grafana, Grafana Agent, OpenTelemetry Collector, Loki, Mimir, Alloy, and Promtail with Ansible. 8 | links: 9 | - title: Slack Channel 10 | url: https://raintank-corp.slack.com/archives/C04T7GX8C69 11 | annotations: 12 | backstage.io/techdocs-ref: dir:. 13 | github.com/project-slug: grafana/grafana-ansible-collection 14 | spec: 15 | type: tool 16 | owner: group:default/devex 17 | lifecycle: production 18 | -------------------------------------------------------------------------------- /changelogs/.plugin-cache.yaml: -------------------------------------------------------------------------------- 1 | objects: 2 | role: {} 3 | plugins: 4 | become: {} 5 | cache: {} 6 | callback: {} 7 | cliconf: {} 8 | connection: {} 9 | filter: {} 10 | httpapi: {} 11 | inventory: {} 12 | lookup: {} 13 | module: 14 | alert_contact_point: 15 | description: Manage Alerting Contact points in Grafana 16 | name: alert_contact_point 17 | namespace: '' 18 | version_added: 0.0.1 19 | alert_notification_policy: 20 | description: Manage Alerting Policies points in Grafana 21 | name: alert_notification_policy 22 | namespace: '' 23 | version_added: 0.0.1 24 | cloud_api_key: 25 | description: Manage Grafana Cloud API keys 26 | name: cloud_api_key 27 | namespace: '' 28 | version_added: 0.0.1 29 | cloud_plugin: 30 | description: Manage Grafana Cloud Plugins 31 | name: cloud_plugin 32 | namespace: '' 33 | version_added: 0.0.1 34 | cloud_stack: 35 | description: Manage Grafana Cloud stack 36 | name: cloud_stack 37 | namespace: '' 38 | version_added: 0.0.1 39 | dashboard: 40 | description: Manage Dashboards in Grafana 41 | name: dashboard 42 | namespace: '' 43 | version_added: 0.0.1 44 | datasource: 45 | description: Manage Data sources in Grafana 46 | name: datasource 47 | namespace: '' 48 | version_added: 0.0.1 49 | folder: 50 | description: Manage Folders in Grafana 51 | name: folder 52 | namespace: '' 53 | version_added: 0.0.1 54 | netconf: {} 55 | shell: {} 56 | strategy: {} 57 | test: {} 58 | vars: {} 59 | version: 5.2.0 60 | -------------------------------------------------------------------------------- /changelogs/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | changelog_filename_template: ../CHANGELOG.rst 3 | changelog_filename_version_depth: 0 4 | changes_file: changelog.yaml 5 | changes_format: combined 6 | ignore_other_fragment_extensions: true 7 | keep_fragments: false 8 | mention_ancestor: true 9 | new_plugins_after_name: removed_features 10 | notesdir: fragments 11 | prelude_section_name: release_summary 12 | prelude_section_title: Release Summary 13 | sanitize_changelog: true 14 | sections: 15 | - - major_changes 16 | - Major Changes 17 | - - minor_changes 18 | - Minor Changes 19 | - - breaking_changes 20 | - Breaking Changes / Porting Guide 21 | - - deprecated_features 22 | - Deprecated Features 23 | - - removed_features 24 | - Removed Features (previously deprecated) 25 | - - security_fixes 26 | - Security Fixes 27 | - - bugfixes 28 | - Bugfixes 29 | - - known_issues 30 | - Known Issues 31 | title: Grafana.Grafana 32 | trivial_section_name: trivial 33 | use_fqcn: true 34 | -------------------------------------------------------------------------------- /examples/agent-basic-no-options.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: true 4 | # pre_tasks happen before roles are executed / applied 5 | pre_tasks: [] 6 | # roles are ran after pre_tasks 7 | roles: 8 | - grafana_agent 9 | # tasks are ran after roles 10 | tasks: [] 11 | -------------------------------------------------------------------------------- /examples/agent-mode-flow-with-dynamic-conf.yaml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | tasks: 3 | - name: Install Grafana Agent 4 | ansible.builtin.include_role: 5 | name: grafana.grafana.grafana_agent 6 | vars: 7 | grafana_agent_mode: flow 8 | # Change config file on the host to .river 9 | grafana_agent_config_filename: config.river 10 | # Remove default flags 11 | grafana_agent_flags_extra: 12 | server.http.listen-addr: '0.0.0.0:12345' -------------------------------------------------------------------------------- /examples/agent-send-to-grafana-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: true 4 | vars: 5 | grafana_agent_metrics_config: 6 | global: 7 | external_labels: 8 | datacenter: primary 9 | cluster: my-cluster 10 | instance: "{{ ansible_host }}" 11 | remote_write: 12 | - url: https://prometheus-.grafana.net/api/prom/push 13 | basic_auth: 14 | username: "1234567" # your username / instanceID 15 | password: "..." # your grafana.com token 16 | configs: 17 | - name: local 18 | scrape_configs: 19 | # scrape a an application on the localhost 20 | - job_name: my-app 21 | metrics_path: /metrics 22 | static_configs: 23 | - targets: 24 | - localhost:8080 25 | relabel_configs: [] 26 | metric_relabel_configs: [] 27 | 28 | grafana_agent_logs_config: 29 | global: 30 | clients: 31 | - url: https://logs-.grafana.net/loki/api/v1/push 32 | basic_auth: 33 | username: "1234567" # your username / instanceID 34 | password: "..." # your grafana.com token 35 | configs: 36 | - name: local 37 | positions: 38 | filename: /tmp/positions.yaml 39 | target_config: 40 | sync_period: 10s 41 | scrape_configs: 42 | # scrape all of the log files in /var/log on the localhost 43 | - job_name: log-files 44 | static_configs: 45 | - targets: 46 | - localhost 47 | labels: 48 | job: var-logs 49 | instance: "{{ ansible_host }}" 50 | __path__: /var/log/*.log 51 | # scrape all of the journal logs on localhost 52 | - job_name: systemd-journal 53 | journal: 54 | max_age: 12h 55 | labels: 56 | job: systemd-journal 57 | relabel_configs: 58 | - source_labels: 59 | - __journal__systemd_unit 60 | target_label: systemd_unit 61 | - source_labels: 62 | - __journal__hostname 63 | target_label: hostname 64 | - source_labels: 65 | - __journal_syslog_identifier 66 | target_label: syslog_identifier 67 | - source_labels: 68 | - __journal__pid 69 | target_label: pid 70 | - source_labels: 71 | - __journal__uid 72 | target_label: uid 73 | - source_labels: 74 | - __journal__transport 75 | target_label: transport 76 | grafana_agent_integrations_config: 77 | scrape_integrations: true 78 | # get metrics about the agent 79 | agent: 80 | enabled: true 81 | relabel_configs: [] 82 | metric_relabel_configs: [] 83 | # get node exporter metrics 84 | node_exporter: 85 | enabled: true 86 | relabel_configs: [] 87 | metric_relabel_configs: [] 88 | 89 | # pre_tasks happen before roles are executed / applied 90 | pre_tasks: [] 91 | # roles are ran after pre_tasks 92 | roles: 93 | - grafana_agent 94 | # tasks are ran after roles 95 | tasks: [] 96 | -------------------------------------------------------------------------------- /examples/alloy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deploy alloy 3 | hosts: all 4 | become: true 5 | roles: 6 | - role: grafana.grafana.alloy 7 | tasks: 8 | - name: Deploy alloy 9 | ansible.builtin.include_role: 10 | name: grafana.grafana.alloy 11 | vars: 12 | alloy_config: | 13 | prometheus.scrape "default" { 14 | targets = [{"__address__" = "127.0.0.1:12345"}] 15 | forward_to = [prometheus.remote_write.prom.receiver] 16 | } 17 | prometheus.remote_write "prom" { 18 | endpoint { 19 | url = "http://mimir:9009/api/v1/push" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | # (string) Sets the macro for the 'ansible_managed' variable available for :ref:`ansible_collections.ansible.builtin.template_module` and :ref:`ansible_collections.ansible.windows.win_template_module`. This is only relevant for those two modules. 3 | ansible_managed="Ansible managed file. Be wary of possible overwrites." 4 | 5 | # (boolean) Toggle to control the showing of deprecation warnings 6 | deprecation_warnings=False 7 | 8 | # (boolean) Set this to "False" if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host 9 | host_key_checking=False 10 | 11 | # (pathlist) Comma separated list of Ansible inventory sources 12 | inventory=hosts 13 | 14 | # (pathspec) Colon separated paths in which Ansible will search for Modules. 15 | library=../plugins/modules 16 | 17 | # (path) File to which Ansible will log on the controller. When empty logging is disabled. 18 | log_path=./ansible.log 19 | 20 | # (pathspec) Colon separated paths in which Ansible will search for Roles. 21 | roles_path=../roles 22 | 23 | [ssh_connection] 24 | 25 | # ssh arguments to use 26 | # Leaving off ControlPersist will result in poor performance, so use 27 | # paramiko on older platforms rather than removing it 28 | ssh_args = -o ControlMaster=auto -o ControlPersist=60s 29 | 30 | # if True, make ansible use scp if the connection type is ssh 31 | # (default is sftp) 32 | scp_if_ssh = True 33 | -------------------------------------------------------------------------------- /examples/loki-basic-no-options.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deploy Loki using the default configuration 3 | hosts: all 4 | become: true 5 | roles: 6 | - role: grafana.grafana.loki 7 | -------------------------------------------------------------------------------- /examples/loki-local-filesystem-with-retention-and-alert.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deploy Loki using the local filesystem 3 | hosts: all 4 | become: true 5 | roles: 6 | - role: grafana.grafana.loki 7 | vars: 8 | loki_querier: 9 | max_concurrent: 16 10 | engine: 11 | max_look_back_period: 8760h 12 | loki_storage_config: 13 | tsdb_shipper: 14 | active_index_directory: "{{ loki_working_path }}/tsdb-index" 15 | cache_location: "{{ loki_working_path }}/tsdb-cache" 16 | filesystem: 17 | directory: "{{ loki_working_path }}/chunks" 18 | loki_ingester: 19 | wal: 20 | enabled: true 21 | dir: "{{ loki_working_path }}/wal" 22 | lifecycler: 23 | address: 127.0.0.1 24 | ring: 25 | kvstore: 26 | store: inmemory 27 | replication_factor: 1 28 | final_sleep: 0s 29 | chunk_idle_period: 1h 30 | max_chunk_age: 2h 31 | chunk_target_size: 1048576 32 | query_store_max_look_back_period: 8760h 33 | loki_limits_config: 34 | split_queries_by_interval: 0 35 | reject_old_samples: true 36 | reject_old_samples_max_age: 168h 37 | max_query_length: 0 38 | max_query_series: 50000 39 | retention_period: 8760h 40 | allow_structured_metadata: false 41 | max_query_lookback: 8760h 42 | loki_compactor: 43 | working_directory: "{{ loki_working_path }}/compactor" 44 | compaction_interval: 10m 45 | retention_enabled: true 46 | retention_delete_delay: 2h 47 | retention_delete_worker_count: 150 48 | delete_request_store: filesystem 49 | loki_common: 50 | path_prefix: "{{ loki_working_path }}" 51 | storage: 52 | filesystem: 53 | rules_directory: "{{ loki_working_path }}/rules" 54 | replication_factor: 1 55 | ring: 56 | instance_addr: 127.0.0.1 57 | kvstore: 58 | store: inmemory 59 | loki_ruler_alerts: 60 | - name: Logs.sshd 61 | rules: 62 | - alert: SshLoginFailed 63 | expr: | 64 | count_over_time({job=~"secure"} |="sshd[" |~": Failed|: Invalid|: Connection closed by authenticating user" | __error__="" [15m]) > 6 65 | for: 0m 66 | labels: 67 | severity: critical 68 | annotations: 69 | summary: "{% raw %}SSH authentication failure (instance {{ $labels.instance }}).{% endraw %}" 70 | description: "{% raw %}Increase of SSH authentication failures in last 15 minutes\\n VALUE = {{ $value }}{% endraw %}" 71 | -------------------------------------------------------------------------------- /examples/mimir-3-hosts.yaml: -------------------------------------------------------------------------------- 1 | - name: Install mimir 2 | hosts: [mimir-1, mimir-2, mimir-3] 3 | become: true 4 | 5 | tasks: 6 | - name: Install mimir 7 | ansible.builtin.include_role: 8 | name: grafana.grafana.mimir 9 | vars: 10 | # Run against minio blob store backed, see readme for local setup or mimir docs for Azure, AWS, etc. 11 | mimir_storage: 12 | storage: 13 | backend: s3 14 | s3: 15 | endpoint: localhost:9000 16 | access_key_id: testtest 17 | secret_access_key: testtest 18 | insecure: true 19 | bucket_name: mimir 20 | 21 | # Blocks storage requires a prefix when using a common object storage bucket. 22 | mimir_blocks_storage: 23 | storage_prefix: blocks 24 | tsdb: 25 | dir: "{{ mimir_working_path}}/ingester" 26 | 27 | # Use memberlist, a gossip-based protocol, to enable the 3 Mimir replicas to communicate 28 | mimir_memberlist: 29 | join_members: 30 | - mimir-1:7946 31 | - mimir-2:7946 32 | - mimir-3:7946 33 | -------------------------------------------------------------------------------- /examples/opentelemetry-collector.yml: -------------------------------------------------------------------------------- 1 | - name: Install OpenTelemetry Collector 2 | hosts: all 3 | become: true 4 | 5 | tasks: 6 | - name: Install OpenTelemetry Collector 7 | ansible.builtin.include_role: 8 | name: grafana.grafana.opentelemetry_collector 9 | vars: 10 | otel_collector_receivers: 11 | otlp: 12 | protocols: 13 | grpc: 14 | endpoint: 0.0.0.0:4317 15 | http: 16 | endpoint: 0.0.0.0:4318 17 | otel_collector_processors: 18 | batch: 19 | 20 | otel_collector_exporters: 21 | otlp: 22 | endpoint: otelcol:4317 23 | 24 | otel_collector_extensions: 25 | health_check: 26 | pprof: 27 | zpages: 28 | 29 | otel_collector_service: 30 | extensions: [health_check, pprof, zpages] 31 | pipelines: 32 | traces: 33 | receivers: [otlp] 34 | processors: [batch] 35 | exporters: [otlp] 36 | metrics: 37 | receivers: [otlp] 38 | processors: [batch] 39 | exporters: [otlp] 40 | logs: 41 | receivers: [otlp] 42 | processors: [batch] 43 | exporters: [otlp] 44 | -------------------------------------------------------------------------------- /examples/promtail-multiple-logs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deploy Promtail to ship logs to the local Loki instance 3 | hosts: all 4 | become: true 5 | roles: 6 | - role: grafana.grafana.promtail 7 | vars: 8 | promtail_clients: 9 | - url: http://localhost:3100/loki/api/v1/push 10 | promtail_scrape_configs: 11 | - job_name: system 12 | static_configs: 13 | - targets: 14 | - localhost 15 | labels: 16 | job: messages 17 | instance: "{{ ansible_facts['fqdn'] }}" 18 | __path__: /var/log/messages 19 | - targets: 20 | - localhost 21 | labels: 22 | job: nginx 23 | instance: "{{ ansible_facts['fqdn'] }}" 24 | __path__: /var/log/nginx/*.log 25 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | namespace: grafana 3 | name: grafana 4 | version: 6.0.1 5 | readme: README.md 6 | authors: 7 | - Grafana Labs 8 | - Ishan Jain 9 | - Gerard van Engelen 10 | description: Ansible collection to manage Grafana resources 11 | license: 12 | - GPL-3.0-or-later 13 | tags: [grafana, observability, monitoring] 14 | repository: https://github.com/grafana/grafana-ansible-collection 15 | issues: https://github.com/grafana/grafana-ansible-collection/issues 16 | documentation: https://docs.ansible.com/ansible/latest/collections/grafana/grafana/index.html 17 | build_ignore: 18 | - node_modules/* 19 | -------------------------------------------------------------------------------- /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | --- 2 | requires_ansible: ">=2.12.0,<3.0.0" 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grafana-ansible-collection", 3 | "version": "2.1.4", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/grafana/grafana-ansible-collection.git" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "bugs": { 15 | "url": "https://github.com/grafana/grafana-ansible-collection/issues" 16 | }, 17 | "homepage": "https://github.com/grafana/grafana-ansible-collection#readme", 18 | "dependencies": { 19 | "editorconfig-checker": "^5.0.1", 20 | "markdownlint-cli2": "^0.6.0", 21 | "textlint": "^12.5.1", 22 | "textlint-rule-common-misspellings": "^1.0.1", 23 | "textlint-rule-no-todo": "^2.0.1", 24 | "textlint-rule-terminology": "^3.0.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | yamllint==1.35.1 2 | ansible-lint>=6.13.1, <25.0.0 3 | pylint>=2.16.2,<4.0.0 4 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - name: https://github.com/ansible-collections/community.general.git 4 | type: git 5 | - name: https://github.com/ansible-collections/community.grafana.git 6 | type: git 7 | - name: https://github.com/ansible-collections/ansible.posix.git 8 | type: git 9 | -------------------------------------------------------------------------------- /roles/alloy/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for alloy 3 | alloy_version: "latest" 4 | alloy_uninstall: false 5 | alloy_expose_port: false 6 | alloy_download_url_rpm: "https://github.com/grafana/alloy/releases/download/v{{ alloy_version }}/alloy-{{ alloy_version }}-1.{{ __alloy_arch }}.rpm" 7 | alloy_download_url_deb: "https://github.com/grafana/alloy/releases/download/v{{ alloy_version }}/alloy-{{ alloy_version }}-1.{{ __alloy_arch }}.deb" 8 | alloy_readiness_check_use_https: false 9 | alloy_readiness_check_use_proxy: true 10 | 11 | alloy_user_groups: [] 12 | # alloy_user_groups: 13 | # - "systemd-journal" 14 | 15 | alloy_env_file_vars: {} 16 | # alloy_env_file_vars: 17 | # CONFIG_FILE: "/custom/path" 18 | # CUSTOM_ARGS: "--server.http.listen-addr=0.0.0.0:12345 --stability.level=public-preview --feature.community-components.enabled=true" 19 | 20 | alloy_systemd_override: {} 21 | # alloy_systemd_override: | 22 | # [Service] 23 | # User=root 24 | 25 | alloy_config: {} 26 | # alloy_config: | 27 | # prometheus.scrape "default" { 28 | # targets = [{"__address__" = "127.0.0.1:12345"}] 29 | # forward_to = [prometheus.remote_write.prom.receiver] 30 | # } 31 | # prometheus.remote_write "prom" { 32 | # endpoint { 33 | # url = "http://mimir:9009/api/v1/push" 34 | # } 35 | # } 36 | -------------------------------------------------------------------------------- /roles/alloy/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for alloy 3 | - name: Restart alloy 4 | listen: "restart alloy" 5 | ansible.builtin.systemd: 6 | daemon_reload: true 7 | name: alloy.service 8 | state: restarted 9 | enabled: true 10 | when: not ansible_check_mode 11 | -------------------------------------------------------------------------------- /roles/alloy/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: alloy 4 | author: Ishan Jain, voidquark 5 | description: Role to Install and Configure Grafana Alloy 6 | license: "GPL-3.0-or-later" 7 | min_ansible_version: "2.13" 8 | platforms: 9 | - name: EL 10 | versions: 11 | - "8" 12 | - "9" 13 | - name: Fedora 14 | versions: 15 | - all 16 | - name: Debian 17 | versions: 18 | - all 19 | - name: Ubuntu 20 | versions: 21 | - all 22 | galaxy_tags: 23 | - grafana 24 | - observability 25 | - monitoring 26 | - opentelemetry 27 | - telemetry 28 | -------------------------------------------------------------------------------- /roles/alloy/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | vars: 5 | alloy_version: "1.4.2" 6 | alloy_config: | 7 | prometheus.scrape "default" { 8 | targets = [{"__address__" = "127.0.0.1:12345"}] 9 | forward_to = [prometheus.remote_write.prom.receiver] 10 | } 11 | prometheus.remote_write "prom" { 12 | endpoint { 13 | url = "http://mimir:9009/api/v1/push" 14 | } 15 | } 16 | roles: 17 | - role: grafana.grafana.alloy 18 | -------------------------------------------------------------------------------- /roles/alloy/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | ignore-errors: true 6 | driver: 7 | name: docker 8 | platforms: 9 | - name: instance 10 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest" 11 | command: ${MOLECULE_DOCKER_COMMAND:-""} 12 | volumes: 13 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 14 | cgroupns_mode: host 15 | privileged: true 16 | pre_build_image: true 17 | provisioner: 18 | name: ansible 19 | playbooks: 20 | converge: converge.yml 21 | -------------------------------------------------------------------------------- /roles/alloy/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for alloy 3 | - name: Include OS specific variables 4 | ansible.builtin.include_vars: 5 | file: "{{ ansible_facts['os_family'] }}.yml" 6 | 7 | - name: Preflight 8 | ansible.builtin.include_tasks: 9 | file: "preflight.yml" 10 | 11 | - name: Deploy Alloy service 12 | ansible.builtin.include_tasks: 13 | file: "deploy.yml" 14 | when: not alloy_uninstall 15 | 16 | - name: Uninstall Alloy service 17 | ansible.builtin.include_tasks: 18 | file: "uninstall.yml" 19 | when: alloy_uninstall 20 | -------------------------------------------------------------------------------- /roles/alloy/tasks/preflight.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Fail when alloy_config or alloy_env_file_vars.CONFIG_FILE is not defined 3 | ansible.builtin.fail: 4 | msg: Variable alloy_config or alloy_env_file_vars.CONFIG_FILE is required! 5 | when: 6 | - alloy_config | length < 1 7 | - alloy_env_file_vars.CONFIG_FILE is not defined 8 | - not alloy_uninstall 9 | 10 | - name: Extract IP address and PORT from alloy_env_file_vars 11 | when: alloy_env_file_vars.CUSTOM_ARGS is defined and alloy_env_file_vars.CUSTOM_ARGS | length > 0 12 | block: 13 | - name: Search for server.http.listen-addr string 14 | ansible.builtin.set_fact: 15 | __alloy_server_http_listen_addr_regex: "{{ alloy_env_file_vars.CUSTOM_ARGS | regex_search('--server.http.listen-addr=([\\d\\.]+):(\\d+)', '\\1', '\\2') }}" 16 | 17 | - name: Extract IP address and port 18 | ansible.builtin.set_fact: 19 | __alloy_server_http_listen_address: "{{ __alloy_server_http_listen_addr_regex[0] }}" 20 | __alloy_server_http_listen_port: "{{ __alloy_server_http_listen_addr_regex[1] }}" 21 | when: __alloy_server_http_listen_addr_regex | length > 0 22 | 23 | - name: Assert that extracted IP address is valid 24 | ansible.builtin.assert: 25 | that: __alloy_server_http_listen_address | ansible.utils.ipaddr 26 | when: __alloy_server_http_listen_addr_regex | length > 0 27 | -------------------------------------------------------------------------------- /roles/alloy/tasks/setup-Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: APT - Install Alloy 3 | ansible.builtin.apt: 4 | deb: "{{ alloy_download_url_deb }}" 5 | state: present 6 | notify: restart alloy 7 | when: __current_deployed_version.stdout is not defined or alloy_version not in __current_deployed_version.stdout 8 | -------------------------------------------------------------------------------- /roles/alloy/tasks/setup-RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: DNF - Install Alloy from remote URL 3 | ansible.builtin.dnf: 4 | name: "{{ alloy_download_url_rpm }}" 5 | state: present 6 | disable_gpg_check: true 7 | notify: restart alloy 8 | when: __current_deployed_version.stdout is not defined or alloy_version not in __current_deployed_version.stdout 9 | -------------------------------------------------------------------------------- /roles/alloy/tasks/uninstall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Stop Alloy service 3 | ansible.builtin.systemd: # noqa ignore-errors 4 | name: alloy 5 | state: stopped 6 | ignore_errors: true 7 | 8 | - name: Uninstall Alloy rpm package 9 | ansible.builtin.dnf: 10 | name: "alloy" 11 | state: absent 12 | autoremove: true 13 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 14 | 15 | - name: Uninstall Alloy deb package 16 | ansible.builtin.apt: 17 | name: "alloy" 18 | state: absent 19 | purge: true 20 | when: ansible_facts['os_family'] == 'Debian' 21 | 22 | - name: Ensure that Alloy firewalld rule is not present - tcp port {{ __alloy_server_http_listen_port }} 23 | ansible.posix.firewalld: # noqa ignore-errors 24 | immediate: true 25 | permanent: true 26 | port: "{{ __alloy_server_http_listen_port }}/tcp" 27 | state: disabled 28 | ignore_errors: true 29 | 30 | - name: Remove Alloy directories 31 | ansible.builtin.file: 32 | path: "{{ remove_me }}" 33 | state: absent 34 | loop: 35 | - "/etc/alloy" 36 | - "/etc/systemd/system/alloy.service.d" 37 | - "/var/lib/alloy" 38 | - "/etc/sysconfig/alloy" 39 | - "/etc/default/alloy" 40 | loop_control: 41 | loop_var: remove_me 42 | 43 | - name: Remove the Alloy system user 44 | ansible.builtin.user: 45 | name: "alloy" 46 | force: true 47 | state: absent 48 | 49 | - name: Remove Alloy system group 50 | ansible.builtin.group: 51 | name: "alloy" 52 | state: absent 53 | -------------------------------------------------------------------------------- /roles/alloy/templates/alloy.j2: -------------------------------------------------------------------------------- 1 | # Ansible Managed 2 | 3 | {% if alloy_env_file_vars.CONFIG_FILE is not defined or alloy_env_file_vars.CONFIG_FILE | length < 1 %} 4 | CONFIG_FILE="/etc/alloy/config.alloy" 5 | {% endif %} 6 | RESTART_ON_UPGRADE=true 7 | 8 | {% for key, value in alloy_env_file_vars.items() %} 9 | {{ key}}="{{value}}" 10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /roles/alloy/templates/config.alloy.j2: -------------------------------------------------------------------------------- 1 | // Ansible Managed 2 | 3 | {{ alloy_config }} 4 | -------------------------------------------------------------------------------- /roles/alloy/templates/override.conf.j2: -------------------------------------------------------------------------------- 1 | # Ansible Managed 2 | 3 | {{ alloy_systemd_override }} 4 | -------------------------------------------------------------------------------- /roles/alloy/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __alloy_env_file: "/etc/default/alloy" 3 | -------------------------------------------------------------------------------- /roles/alloy/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __alloy_env_file: "/etc/sysconfig/alloy" 3 | -------------------------------------------------------------------------------- /roles/alloy/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __alloy_server_http_listen_address: 127.0.0.1 3 | __alloy_server_http_listen_port: 12345 4 | __alloy_arch_map: 5 | x86_64: 'amd64' 6 | armv6l: 'arm' 7 | armv7l: 'arm' 8 | aarch64: 'arm64' 9 | __alloy_arch: "{{ __alloy_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}" 10 | -------------------------------------------------------------------------------- /roles/grafana/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Restart grafana" 3 | ansible.builtin.service: 4 | name: grafana-server 5 | state: restarted 6 | become: true 7 | listen: "restart_grafana" 8 | tags: 9 | - grafana_run 10 | 11 | - name: "Set privileges on provisioned dashboards" 12 | ansible.builtin.file: 13 | path: "{{ grafana_ini.paths.data }}/dashboards" 14 | recurse: true 15 | owner: "grafana" 16 | group: "grafana" 17 | mode: "0640" 18 | become: true 19 | listen: "provisioned dashboards changed" 20 | 21 | - name: "Set privileges on provisioned dashboards directory" 22 | ansible.builtin.file: 23 | path: "{{ grafana_ini.paths.data }}/dashboards" 24 | state: "directory" 25 | recurse: false 26 | mode: "0755" 27 | become: true 28 | listen: "provisioned dashboards changed" 29 | 30 | - name: "Find dashboards subdirectories" 31 | ansible.builtin.find: 32 | paths: "{{ grafana_ini.paths.data }}/dashboards" 33 | recurse: yes 34 | file_type: directory 35 | register: __dashboards_subdirs 36 | become: true 37 | listen: "provisioned dashboards changed" 38 | 39 | - name: "Set privileges on provisioned dashboards sub-directories" 40 | ansible.builtin.file: 41 | path: "{{ item }}" 42 | state: "directory" 43 | recurse: false 44 | mode: "0755" 45 | with_items: 46 | - "{{ __dashboards_subdirs.files | map(attribute='path') | list }}" 47 | become: true 48 | listen: "provisioned dashboards changed" 49 | -------------------------------------------------------------------------------- /roles/grafana/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: "Grafana" 4 | description: "Grafana - platform for analytics and monitoring" 5 | license: "GPL-3.0-or-later" 6 | min_ansible_version: "2.9" 7 | platforms: 8 | - name: Ubuntu 9 | versions: 10 | - bionic 11 | - xenial 12 | - name: Debian 13 | versions: 14 | - stretch 15 | - buster 16 | - name: EL 17 | versions: 18 | - "7" 19 | - "8" 20 | - name: Fedora 21 | versions: 22 | - "30" 23 | - "31" 24 | galaxy_tags: 25 | - grafana 26 | - dashboard 27 | - alerts 28 | - alerting 29 | - presentation 30 | - monitoring 31 | - metrics 32 | -------------------------------------------------------------------------------- /roles/grafana/molecule/alternative/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Run role" 3 | hosts: all 4 | any_errors_fatal: true 5 | roles: 6 | - grafana.grafana.grafana 7 | vars: 8 | grafana_version: 6.2.5 9 | grafana_ini: 10 | security: 11 | admin_user: admin 12 | admin_password: "password" 13 | server: 14 | http_addr: "127.0.0.1" 15 | auth: 16 | login_maximum_inactive_lifetime_days: 42 17 | disable_login_form: false 18 | oauth_auto_login: false 19 | disable_signout_menu: false 20 | signout_redirect_url: "" 21 | anonymous: 22 | org_name: "Main Organization" 23 | org_role: Viewer 24 | ldap: 25 | config_file: "/etc/grafana/ldap.toml" 26 | allow_sign_up: false 27 | basic: 28 | enabled: true 29 | log: 30 | mode: syslog 31 | level: warn 32 | grafana_ldap: 33 | verbose_logging: false 34 | servers: 35 | host: 127.0.0.1 36 | port: 389 37 | use_ssl: false 38 | start_tls: false 39 | ssl_skip_verify: false 40 | root_ca_cert: /path/to/certificate.crt 41 | bind_dn: "cn=admin,dc=grafana,dc=org" 42 | bind_password: grafana 43 | search_filter: "(cn=%s)" 44 | search_base_dns: 45 | - "dc=grafana,dc=org" 46 | group_search_filter: "(&(objectClass=posixGroup)(memberUid=%s))" 47 | group_search_base_dns: 48 | - "ou=groups,dc=grafana,dc=org" 49 | attributes: 50 | name: givenName 51 | surname: sn 52 | username: sAMAccountName 53 | member_of: memberOf 54 | email: mail 55 | group_mappings: 56 | - name: "Main Organization" 57 | id: 1 58 | groups: 59 | - group_dn: "cn=admins,ou=groups,dc=grafana,dc=org" 60 | org_role: Admin 61 | - group_dn: "cn=editors,ou=groups,dc=grafana,dc=org" 62 | org_role: Editor 63 | - group_dn: "*" 64 | org_role: Viewer 65 | - name: "Alternative Org" 66 | id: 2 67 | groups: 68 | - group_dn: "cn=alternative_admins,ou=groups,dc=grafana,dc=org" 69 | org_role: Admin 70 | grafana_api_keys: 71 | - name: "admin" 72 | role: "Admin" 73 | - name: "viewer" 74 | role: "Viewer" 75 | - name: "editor" 76 | role: "Editor" 77 | grafana_api_keys_dir: "/tmp/grafana/keys" 78 | grafana_plugins: 79 | - raintank-worldping-app 80 | grafana_alert_notifications: 81 | notifiers: 82 | - name: "Email Alert" 83 | type: "email" 84 | uid: notifier1 85 | is_default: true 86 | settings: 87 | addresses: "example@example.com" 88 | grafana_dashboards: 89 | - dashboard_id: '1860' 90 | revision_id: '4' 91 | datasource: 'Prometheus' 92 | - dashboard_id: '358' 93 | revision_id: '1' 94 | datasource: 'Prometheus' 95 | grafana_datasources: 96 | - name: "Prometheus" 97 | type: "prometheus" 98 | access: "proxy" 99 | url: "http://prometheus.mydomain" 100 | basicAuth: true 101 | basicAuthUser: "admin" 102 | basicAuthPassword: "password" 103 | isDefault: true 104 | jsonData: 105 | tlsAuth: false 106 | tlsAuthWithCACert: false 107 | tlsSkipVerify: true 108 | -------------------------------------------------------------------------------- /roles/grafana/molecule/alternative/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /roles/grafana/molecule/alternative/tests/test_alternative.py: -------------------------------------------------------------------------------- 1 | from __future__ import (absolute_import, division, print_function) 2 | __metaclass__ = type 3 | 4 | import os 5 | import testinfra.utils.ansible_runner 6 | 7 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 8 | os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') 9 | 10 | 11 | def test_directories(host): 12 | dirs = [ 13 | "/etc/grafana", 14 | "/var/log/grafana", 15 | "/var/lib/grafana", 16 | "/var/lib/grafana/dashboards", 17 | "/var/lib/grafana/plugins", 18 | "/var/lib/grafana/plugins/raintank-worldping-app" 19 | ] 20 | files = [ 21 | "/etc/grafana/grafana.ini", 22 | "/etc/grafana/ldap.toml" 23 | ] 24 | for directory in dirs: 25 | d = host.file(directory) 26 | assert d.is_directory 27 | assert d.exists 28 | for file in files: 29 | f = host.file(file) 30 | assert f.exists 31 | assert f.is_file 32 | 33 | 34 | def test_service(host): 35 | s = host.service("grafana-server") 36 | # assert s.is_enabled 37 | assert s.is_running 38 | 39 | 40 | def test_packages(host): 41 | p = host.package("grafana") 42 | assert p.is_installed 43 | assert p.version == "6.2.5" 44 | 45 | 46 | def test_socket(host): 47 | assert host.socket("tcp://127.0.0.1:3000").is_listening 48 | 49 | 50 | def test_custom_auth_option(host): 51 | f = host.file("/etc/grafana/grafana.ini") 52 | assert f.contains("login_maximum_inactive_lifetime_days = 42") 53 | -------------------------------------------------------------------------------- /roles/grafana/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Run role" 3 | hosts: all 4 | any_errors_fatal: true 5 | roles: 6 | - grafana.grafana.grafana 7 | vars: 8 | grafana_ini: 9 | security: 10 | admin_user: admin 11 | admin_password: password 12 | -------------------------------------------------------------------------------- /roles/grafana/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /roles/grafana/molecule/default/tests/test_default.py: -------------------------------------------------------------------------------- 1 | from __future__ import (absolute_import, division, print_function) 2 | __metaclass__ = type 3 | 4 | import os 5 | import testinfra.utils.ansible_runner 6 | 7 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 8 | os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') 9 | 10 | 11 | def test_directories(host): 12 | dirs = [ 13 | "/etc/grafana", 14 | "/var/log/grafana", 15 | "/var/lib/grafana", 16 | "/var/lib/grafana/dashboards", 17 | "/var/lib/grafana/plugins" 18 | ] 19 | files = [ 20 | "/etc/grafana/grafana.ini" 21 | ] 22 | for directory in dirs: 23 | d = host.file(directory) 24 | assert d.is_directory 25 | assert d.exists 26 | for file in files: 27 | f = host.file(file) 28 | assert f.exists 29 | assert f.is_file 30 | 31 | 32 | def test_service(host): 33 | s = host.service("grafana-server") 34 | # assert s.is_enabled 35 | assert s.is_running 36 | 37 | 38 | def test_packages(host): 39 | p = host.package("grafana") 40 | assert p.is_installed 41 | 42 | 43 | def test_socket(host): 44 | assert host.socket("tcp://0.0.0.0:3000").is_listening 45 | 46 | 47 | def test_yum_repo(host): 48 | if host.system_info.distribution in ['centos', 'redhat', 'fedora']: 49 | f = host.file("/etc/yum.repos.d/grafana.repo") 50 | assert f.exists 51 | -------------------------------------------------------------------------------- /roles/grafana/tasks/api_keys.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Ensure grafana key directory exists" 3 | ansible.builtin.file: 4 | path: "{{ grafana_api_keys_dir }}/{{ inventory_hostname }}" 5 | state: directory 6 | mode: "0755" 7 | become: false 8 | delegate_to: localhost 9 | 10 | - name: "Check api key list" 11 | ansible.builtin.uri: 12 | url: "{{ grafana_api_url }}/api/auth/keys" 13 | user: "{{ grafana_ini.security.admin_user }}" 14 | password: "{{ grafana_ini.security.admin_password }}" 15 | force_basic_auth: true 16 | return_content: true 17 | register: __existing_api_keys 18 | no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}" 19 | 20 | - name: "Create grafana api keys" 21 | ansible.builtin.uri: 22 | url: "{{ grafana_api_url }}/api/auth/keys" 23 | user: "{{ grafana_ini.security.admin_user }}" 24 | password: "{{ grafana_ini.security.admin_password }}" 25 | force_basic_auth: true 26 | method: POST 27 | body_format: json 28 | body: "{{ item | to_json }}" 29 | loop: "{{ grafana_api_keys }}" 30 | register: __new_api_keys 31 | no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}" 32 | when: "((__existing_api_keys['json'] | selectattr('name', 'equalto', item['name'])) | list) | length == 0" 33 | 34 | - name: "Create api keys file to allow the keys to be seen and used by other automation" 35 | ansible.builtin.copy: 36 | dest: "{{ grafana_api_keys_dir }}/{{ inventory_hostname }}/{{ item['item']['name'] }}.key" 37 | content: "{{ item['json']['key'] }}" 38 | backup: false 39 | mode: "0644" 40 | loop: "{{ __new_api_keys['results'] }}" 41 | become: false 42 | delegate_to: localhost 43 | when: "item['json'] is defined" 44 | -------------------------------------------------------------------------------- /roles/grafana/tasks/configure.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Ensure grafana directories exist" 3 | ansible.builtin.file: 4 | path: "{{ item.path }}" 5 | state: "directory" 6 | owner: "{{ item.owner | default('root') }}" 7 | group: "{{ item.group | default('grafana') }}" 8 | mode: "{{ item.mode | default('0755') }}" 9 | loop: 10 | - path: "/etc/grafana" 11 | - path: "/etc/grafana/datasources" 12 | - path: "/etc/grafana/provisioning" 13 | - path: "/etc/grafana/provisioning/datasources" 14 | - path: "/etc/grafana/provisioning/dashboards" 15 | - path: "/etc/grafana/provisioning/notifiers" 16 | - path: "/etc/grafana/provisioning/notification" 17 | - path: "/etc/grafana/provisioning/plugins" 18 | - path: "{{ grafana_ini.paths.logs }}" 19 | owner: grafana 20 | - path: "{{ grafana_ini.paths.data }}" 21 | owner: grafana 22 | - path: "{{ grafana_ini.paths.data }}/dashboards" 23 | owner: grafana 24 | - path: "{{ grafana_ini.paths.data }}/plugins" 25 | owner: grafana 26 | 27 | - name: "Create grafana main configuration file" 28 | ansible.builtin.template: 29 | src: "grafana.ini.j2" 30 | dest: "/etc/grafana/grafana.ini" 31 | owner: "root" 32 | group: "grafana" 33 | mode: "0640" 34 | no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}" 35 | notify: restart_grafana 36 | 37 | - name: "Create grafana LDAP configuration file" 38 | ansible.builtin.template: 39 | src: "ldap.toml.j2" 40 | dest: "{{ grafana_ini.auth.ldap.config_file | default('/etc/grafana/ldap.toml') }}" 41 | owner: "root" 42 | group: "grafana" 43 | mode: "0640" 44 | no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}" 45 | notify: restart_grafana 46 | when: 47 | - "'ldap' in grafana_ini.auth" 48 | - "'enabled' not in grafana_ini.auth.ldap or grafana_ini.auth.ldap.enabled" 49 | 50 | - name: "Enable grafana socket" 51 | when: 52 | - "grafana_ini.server.protocol is defined and grafana_ini.server.protocol == 'socket'" 53 | - "grafana_ini.server.socket | dirname != '/var/run'" 54 | block: 55 | - name: "Create grafana socket directory" 56 | ansible.builtin.file: 57 | path: "{{ grafana_ini.server.socket | dirname }}" 58 | state: "directory" 59 | mode: "0775" 60 | owner: "grafana" 61 | group: "grafana" 62 | 63 | - name: "Ensure grafana socket directory created on startup" 64 | ansible.builtin.template: 65 | src: "tmpfiles.j2" 66 | dest: "/etc/tmpfiles.d/grafana.conf" 67 | owner: "root" 68 | group: "root" 69 | mode: "0644" 70 | 71 | - name: "Enable grafana to ports lower than port 1024" 72 | community.general.capabilities: 73 | path: /usr/sbin/grafana-server 74 | capability: CAP_NET_BIND_SERVICE+ep 75 | state: present 76 | when: 77 | - "grafana_ini.server.http_port | int <= 1024" 78 | - "grafana_cap_net_bind_service" 79 | 80 | - name: Create a directory for overrides.conf unit file if it does not exist 81 | ansible.builtin.file: 82 | path: /etc/systemd/system/grafana-server.service.d 83 | state: directory 84 | mode: '0755' 85 | when: 86 | - "grafana_ini.server.http_port | int <= 1024" 87 | - "grafana_cap_net_bind_service" 88 | 89 | - name: "Enable grafana to ports lower than port 1024 in systemd unitfile" 90 | ansible.builtin.blockinfile: 91 | path: /etc/systemd/system/grafana-server.service.d/overrides.conf 92 | create: true 93 | block: | 94 | [Service] 95 | AmbientCapabilities=CAP_NET_BIND_SERVICE 96 | CapabilityBoundingSet=CAP_NET_BIND_SERVICE 97 | when: 98 | - "grafana_ini.server.http_port | int <= 1024" 99 | - "grafana_cap_net_bind_service" 100 | 101 | - name: "Enable and start Grafana systemd unit" 102 | ansible.builtin.systemd: 103 | name: "grafana-server" 104 | enabled: true 105 | state: started 106 | daemon_reload: true 107 | -------------------------------------------------------------------------------- /roles/grafana/tasks/datasources.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Ensure datasources exist (via API)" 3 | community.grafana.grafana_datasource: 4 | grafana_url: "{{ grafana_api_url }}" 5 | grafana_user: "{{ grafana_ini.security.admin_user }}" 6 | grafana_password: "{{ grafana_ini.security.admin_password }}" 7 | name: "{{ item.name }}" 8 | ds_url: "{{ item.url }}" 9 | ds_type: "{{ item.type }}" 10 | access: "{{ item.access | default(omit) }}" 11 | is_default: "{{ item.isDefault | default(omit) }}" 12 | basic_auth_user: "{{ item.basicAuthUser | default(omit) }}" 13 | basic_auth_password: "{{ item.basicAuthPassword | default(omit) }}" 14 | database: "{{ item.database | default(omit) }}" 15 | user: "{{ item.user | default(omit) }}" 16 | password: "{{ item.password | default(omit) }}" 17 | aws_auth_type: "{{ item.aws_auth_type | default(omit) }}" 18 | aws_default_region: "{{ item.aws_default_region | default(omit) }}" 19 | aws_access_key: "{{ item.aws_access_key | default(omit) }}" 20 | aws_secret_key: "{{ item.aws_secret_key | default(omit) }}" 21 | aws_credentials_profile: "{{ item.aws_credentials_profile | default(omit) }}" 22 | aws_custom_metrics_namespaces: "{{ item.aws_custom_metrics_namespaces | default(omit) }}" 23 | loop: "{{ grafana_datasources }}" 24 | when: "not grafana_use_provisioning" 25 | 26 | - name: "Create/Update datasources file (provisioning)" 27 | ansible.builtin.copy: 28 | dest: "/etc/grafana/provisioning/datasources/ansible.yml" 29 | content: | 30 | apiVersion: 1 31 | deleteDatasources: [] 32 | datasources: 33 | {{ grafana_datasources | to_nice_yaml }} 34 | backup: false 35 | owner: root 36 | group: grafana 37 | mode: 0640 38 | notify: restart_grafana 39 | become: true 40 | when: "grafana_use_provisioning" 41 | -------------------------------------------------------------------------------- /roles/grafana/tasks/notifications.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # legacy config 3 | - name: "Create/Delete/Update alert notifications channels (provisioning)" 4 | ansible.builtin.copy: 5 | content: | 6 | apiVersion: 1 7 | {{ grafana_alert_notifications | to_nice_yaml }} 8 | dest: /etc/grafana/provisioning/notification/ansible.yml 9 | owner: root 10 | group: grafana 11 | mode: "0640" 12 | become: true 13 | notify: restart_grafana 14 | when: grafana_use_provisioning and grafana_alert_notifications | length > 0 15 | 16 | # new alert resources 17 | - name: "Create/Delete/Update alert resources (provisioning)" 18 | ansible.builtin.copy: 19 | content: | 20 | apiVersion: 1 21 | {{ grafana_alert_resources | to_nice_yaml }} 22 | dest: /etc/grafana/provisioning/alerting/ansible.yml 23 | owner: root 24 | group: grafana 25 | mode: "0640" 26 | become: true 27 | notify: restart_grafana 28 | when: grafana_use_provisioning and grafana_alert_resources | length > 0 29 | -------------------------------------------------------------------------------- /roles/grafana/tasks/plugins.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Check which plugins are installed" 3 | ansible.builtin.find: 4 | file_type: directory 5 | recurse: false 6 | paths: "{{ grafana_ini.paths.data }}/plugins" 7 | register: __installed_plugins 8 | 9 | - name: "Install plugins" 10 | become: true 11 | ansible.builtin.command: 12 | cmd: "grafana-cli --pluginsDir {{ grafana_ini.paths.data }}/plugins plugins install {{ item }}" 13 | creates: "{{ grafana_ini.paths.data }}/plugins/{{ item }}" 14 | loop: "{{ grafana_plugins | difference(__installed_plugins.files) }}" 15 | register: __plugin_install 16 | until: "__plugin_install is succeeded" 17 | retries: 5 18 | delay: 2 19 | notify: 20 | - restart_grafana 21 | -------------------------------------------------------------------------------- /roles/grafana/templates/grafana.ini.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # More informations: 3 | # http://docs.grafana.org/installation/configuration 4 | # https://github.com/grafana/grafana/blob/master/conf/sample.ini 5 | 6 | {% for k, v in grafana_ini.items() %} 7 | {% if v is not mapping %} 8 | {{ k }} = {{ v }} 9 | {% endif %} 10 | {% endfor %} 11 | 12 | {% for section, items in grafana_ini.items() %} 13 | {% if items is mapping %} 14 | [{{ section }}] 15 | {% for sub_key, sub_value in items.items() %} 16 | {% if sub_value is mapping %} 17 | 18 | [{{ section }}.{{ sub_key }}] 19 | {% for k, v in sub_value.items() %} 20 | {{ k }} = {{ v }} 21 | {% endfor %} 22 | {% else %} 23 | {{ sub_key }} = {{ sub_value }} 24 | {% endif %} 25 | {% endfor %} 26 | {% endif %} 27 | 28 | {% endfor %} 29 | -------------------------------------------------------------------------------- /roles/grafana/templates/ldap.toml.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Documentation: http://docs.grafana.org/installation/ldap/ 3 | {% if 'verbose_logging' in grafana_ldap %} 4 | verbose_logging = {{ 'true' if grafana_ldap.verbose_logging else 'false' }} 5 | {% endif %} 6 | 7 | [[servers]] 8 | {% for k,v in grafana_ldap.servers.items() if k != 'attributes' %} 9 | {% if k == 'port' %} 10 | {{ k }} = {{ v | int }} 11 | {% elif v in [True, False] %} 12 | {{ k }} = {{ 'true' if v else 'false' }} 13 | {% else %} 14 | {{ k }} = {{ v | to_nice_json }} 15 | {% endif %} 16 | {% endfor %} 17 | 18 | [servers.attributes] 19 | {% for k,v in grafana_ldap.servers.attributes.items() %} 20 | {{ k }} = {{ v | to_nice_json }} 21 | {% endfor %} 22 | 23 | {% for org in grafana_ldap.group_mappings %} 24 | {% if 'name' in org %} 25 | # {{ org.name }} 26 | {% endif %} 27 | {% for group in org.groups %} 28 | [[servers.group_mappings]] 29 | org_id = {{ org.id }} 30 | {% for k,v in group.items() %} 31 | {% if v in [True, False] %} 32 | {{ k }} = {{ 'true' if v else 'false' }} 33 | {% else %} 34 | {{ k }} = "{{ v }}" 35 | {% endif %} 36 | {% endfor %} 37 | 38 | {% endfor %} 39 | {% endfor %} 40 | -------------------------------------------------------------------------------- /roles/grafana/templates/tmpfiles.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | d {{ grafana_ini.server.socket | dirname }} 0775 grafana grafana 3 | -------------------------------------------------------------------------------- /roles/grafana/test-requirements.txt: -------------------------------------------------------------------------------- 1 | molecule 2 | docker 3 | pytest-testinfra 4 | jmespath 5 | selinux 6 | passlib 7 | -------------------------------------------------------------------------------- /roles/grafana/vars/distro/debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | grafana_package: "grafana{% if ansible_facts['architecture'] == 'armv6l' %}-rpi{% endif %}{{ (grafana_version != 'latest') | ternary('=' ~ grafana_version, '') }}" 3 | _grafana_dependencies: 4 | - apt-transport-https 5 | - adduser 6 | - ca-certificates 7 | - libfontconfig 8 | - gnupg2 9 | -------------------------------------------------------------------------------- /roles/grafana/vars/distro/redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | grafana_package: "grafana{{ (grafana_version != 'latest') | ternary('-' ~ grafana_version, '') }}" 3 | # https://unix.stackexchange.com/questions/534463/cant-enable-grafana-on-boot-in-fedora-because-systemd-sysv-install-missing 4 | _grafana_dependencies: 5 | - chkconfig 6 | -------------------------------------------------------------------------------- /roles/grafana/vars/distro/suse.yml: -------------------------------------------------------------------------------- 1 | --- 2 | grafana_package: "grafana{{ (grafana_version != 'latest') | ternary('-' ~ grafana_version, '') }}" 3 | # https://unix.stackexchange.com/questions/534463/cant-enable-grafana-on-boot-in-fedora-because-systemd-sysv-install-missing 4 | # applies to SuSe too 5 | _grafana_dependencies: 6 | - insserv-compat 7 | -------------------------------------------------------------------------------- /roles/grafana_agent/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart Grafana Agent 3 | become: true 4 | ansible.builtin.systemd: 5 | name: grafana-agent 6 | state: restarted 7 | daemon_reload: true 8 | listen: "restart grafana-agent" 9 | 10 | - name: Check Grafana Agent is started properly 11 | ansible.builtin.include_tasks: ga-started.yaml 12 | listen: "restart grafana-agent" 13 | -------------------------------------------------------------------------------- /roles/grafana_agent/meta/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | galaxy_info: 5 | role_name: grafana_agent 6 | author: Ishan Jain 7 | description: Ansible Role to deploy Grafana Agent on Linux hosts. 8 | license: "GPL-3.0-or-later" 9 | min_ansible_version: "2.11" 10 | platforms: 11 | - name: Fedora 12 | versions: 13 | - "all" 14 | - name: Debian 15 | versions: 16 | - "all" 17 | - name: Ubuntu 18 | versions: 19 | - "all" 20 | - name: EL 21 | versions: 22 | - "all" 23 | galaxy_tags: 24 | - grafana 25 | - observability 26 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/configure.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # these tasks are ran in both install and configure, as directories could have changed 3 | - name: Configure directories 4 | ansible.builtin.import_tasks: install/directories.yaml 5 | 6 | - name: Create a symbolic link 7 | ansible.builtin.file: 8 | src: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}" 9 | dest: "/usr/local/bin/{{ grafana_agent_binary }}" 10 | owner: root 11 | group: root 12 | state: link 13 | 14 | - name: Overwrite/Create Grafana Agent service 15 | ansible.builtin.template: 16 | src: grafana-agent.service.j2 17 | dest: "{{ _grafana_agent_systemd_dir }}/{{ _grafana_agent_systemd_unit }}" 18 | owner: root 19 | group: root 20 | mode: 0644 21 | notify: "restart grafana-agent" 22 | 23 | - name: Create the Service Environment file 24 | ansible.builtin.template: 25 | src: EnvironmentFile.j2 26 | dest: "{{ grafana_agent_config_dir }}/{{ grafana_agent_env_file }}" 27 | owner: root 28 | group: "{{ grafana_agent_user_group }}" 29 | mode: 0640 30 | notify: "restart grafana-agent" 31 | 32 | - name: Create Grafana Agent config 33 | ansible.builtin.template: 34 | src: config.yaml.j2 35 | dest: "{{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}" 36 | force: true 37 | owner: root 38 | group: "{{ grafana_agent_user_group }}" 39 | mode: 0640 40 | notify: "restart grafana-agent" 41 | when: grafana_agent_provisioned_config_file | length == 0 42 | 43 | - name: Create Grafana Agent River Config if flow mode for Grafana Agent 44 | ansible.builtin.shell: "AGENT_MODE=flow {{ grafana_agent_install_dir }}/grafana-agent convert -f static {{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }} -o {{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}" 45 | notify: "restart grafana-agent" 46 | when: grafana_agent_provisioned_config_file | length == 0 47 | 48 | - name: Copy Grafana Agent config 49 | ansible.builtin.copy: 50 | src: "{{ grafana_agent_provisioned_config_file }}" 51 | dest: "{{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}" 52 | owner: root 53 | group: "{{ grafana_agent_user_group }}" 54 | mode: 0640 55 | notify: "restart grafana-agent" 56 | when: grafana_agent_provisioned_config_file | length > 0 57 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/ga-started.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Health check Grafana Agent 3 | ansible.builtin.uri: 4 | url: "{{ _grafana_agent_healthcheck_endpoint }}" 5 | follow_redirects: none 6 | method: GET 7 | register: _result 8 | failed_when: false 9 | until: _result.status == 200 10 | retries: 3 11 | delay: 5 12 | changed_when: false 13 | when: not ansible_check_mode 14 | 15 | - name: Check system logs if Grafana Agent is not started 16 | when: not ansible_check_mode and _result.status != 200 17 | block: 18 | - name: Run journalctl 19 | ansible.builtin.shell: 20 | cmd: "journalctl -u grafana-agent -b -n20 --no-pager" 21 | register: journal_ret 22 | changed_when: false 23 | - name: Output Grafana agent logs 24 | ansible.builtin.debug: 25 | var: journal_ret.stdout_lines 26 | - name: Rise alerts 27 | ansible.builtin.assert: 28 | that: false 29 | fail_msg: "Service grafana-agent hasn't started." 30 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/install.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # user and group creation 3 | - name: Configure user groups 4 | ansible.builtin.import_tasks: install/user-group.yaml 5 | 6 | # directory creation 7 | - name: Configure directories 8 | ansible.builtin.import_tasks: install/directories.yaml 9 | 10 | # download and install agent 11 | - name: Download and install Grafana Agent 12 | ansible.builtin.import_tasks: install/download-install.yaml 13 | when: (grafana_agent_local_binary_file is not defined) or (grafana_agent_local_binary_file | length == 0) 14 | 15 | # local install of agent 16 | - name: Local install of Grafana Agent 17 | ansible.builtin.import_tasks: 18 | file: install/local-install.yaml 19 | when: __grafana_agent_local_install 20 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/install/directories.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create install directories 3 | block: 4 | - name: Create Grafana Agent install directory 5 | ansible.builtin.file: 6 | path: "{{ grafana_agent_install_dir }}" 7 | state: directory 8 | owner: root 9 | group: "{{ grafana_agent_user_group }}" 10 | mode: 0770 11 | 12 | - name: Create Grafana Agent conf directory 13 | ansible.builtin.file: 14 | path: "{{ grafana_agent_config_dir }}" 15 | state: directory 16 | owner: root 17 | group: "{{ grafana_agent_user_group }}" 18 | mode: 0770 19 | 20 | - name: Create Grafana Agent data directory 21 | ansible.builtin.file: 22 | path: "{{ grafana_agent_data_dir }}" 23 | state: directory 24 | owner: root 25 | group: "{{ grafana_agent_user_group }}" 26 | mode: 0775 27 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/install/download-install.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Download Grafana Agent binary to controller (localhost) 3 | block: 4 | - name: Create Grafana Agent temp directory 5 | become: false 6 | ansible.builtin.file: 7 | path: "{{ grafana_agent_local_tmp_dir }}" 8 | state: directory 9 | mode: 0751 10 | delegate_to: localhost 11 | check_mode: false 12 | run_once: true 13 | 14 | - name: Download Grafana Agent archive to local folder 15 | become: false 16 | ansible.builtin.get_url: 17 | url: "{{ _grafana_agent_download_url }}" 18 | dest: "{{ grafana_agent_local_tmp_dir }}/grafana-agent_{{ _grafana_agent_cpu_arch }}_{{ grafana_agent_version }}.zip" 19 | mode: 0664 20 | register: _download_archive 21 | until: _download_archive is succeeded 22 | retries: 5 23 | delay: 2 24 | delegate_to: localhost 25 | check_mode: false 26 | run_once: true 27 | 28 | - name: Extract grafana-agent.zip 29 | become: false 30 | ansible.builtin.unarchive: 31 | src: "{{ grafana_agent_local_tmp_dir }}/grafana-agent_{{ _grafana_agent_cpu_arch }}_{{ grafana_agent_version }}.zip" 32 | dest: "{{ grafana_agent_local_tmp_dir }}" 33 | remote_src: false 34 | delegate_to: localhost 35 | run_once: true 36 | 37 | - name: Set local path 38 | ansible.builtin.set_fact: 39 | __grafana_agent_local_binary_file: "{{ grafana_agent_local_tmp_dir }}/{{ grafana_agent_binary }}" 40 | 41 | - name: Propagate downloaded binary 42 | ansible.builtin.copy: 43 | src: "{{ grafana_agent_local_tmp_dir }}/{{ _grafana_agent_download_binary_file }}" 44 | dest: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}" 45 | mode: 0755 46 | owner: root 47 | group: root 48 | when: not ansible_check_mode 49 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/install/local-install.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install from local 3 | block: 4 | - name: "Propagate local binary {{ grafana_agent_local_binary_file }}" 5 | ansible.builtin.copy: 6 | src: "{{ grafana_agent_local_binary_file }}" 7 | dest: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}" 8 | mode: 0755 9 | owner: root 10 | group: root 11 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/install/user-group.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Grafana Agent group creation 3 | block: 4 | - name: "Check if the group exists ({{ grafana_agent_user_group }})" 5 | ansible.builtin.getent: 6 | database: group 7 | key: "{{ grafana_agent_user_group }}" 8 | fail_key: false 9 | 10 | - name: Set whether not the user group exists 11 | ansible.builtin.set_fact: 12 | __grafana_agent_user_group_exists: "{{ ansible_facts.getent_group[grafana_agent_user_group] is not none }}" 13 | 14 | - name: Add user group "{{ grafana_agent_user_group }}" 15 | ansible.builtin.group: 16 | name: "{{ grafana_agent_user_group }}" 17 | system: true 18 | state: present 19 | when: not __grafana_agent_user_group_exists and grafana_agent_user_group != 'root' 20 | 21 | - name: Grafana Agent user group exists 22 | ansible.builtin.debug: 23 | msg: |- 24 | The user group \"{{ grafana_agent_user_group }}\" already exists and will not be modified, 25 | if modifying permissions please perform a separate task 26 | when: __grafana_agent_user_group_exists 27 | 28 | - name: Grafana Agent user creation 29 | block: 30 | - name: Add user "{{ grafana_agent_user }}" 31 | ansible.builtin.user: 32 | name: "{{ grafana_agent_user }}" 33 | comment: "Grafana Agent account" 34 | groups: "{{ [ grafana_agent_user_group ] + grafana_agent_user_groups }}" 35 | system: true 36 | shell: "{{ grafana_agent_user_shell }}" 37 | createhome: "{{ grafana_agent_user_createhome }}" 38 | when: grafana_agent_user != 'root' 39 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Preflight tasks 3 | ansible.builtin.include_tasks: 4 | file: preflight.yaml 5 | apply: 6 | become: true 7 | tags: 8 | - grafana_agent_install 9 | - grafana_agent_configure 10 | - grafana_agent_run 11 | tags: 12 | - grafana_agent_install 13 | - grafana_agent_configure 14 | - grafana_agent_run 15 | 16 | - name: Install tasks 17 | ansible.builtin.include_tasks: 18 | file: install.yaml 19 | apply: 20 | become: true 21 | tags: 22 | - grafana_agent_install 23 | tags: 24 | - grafana_agent_install 25 | 26 | - name: Configuration tasks 27 | ansible.builtin.include_tasks: 28 | file: configure.yaml 29 | apply: 30 | become: true 31 | tags: 32 | - grafana_agent_configure 33 | tags: 34 | - grafana_agent_configure 35 | 36 | - name: Flush handlers 37 | ansible.builtin.meta: flush_handlers 38 | 39 | - name: Ensure Grafana Agent is started and enabled on boot 40 | become: true 41 | ansible.builtin.systemd: 42 | name: grafana-agent 43 | enabled: true 44 | state: started 45 | tags: 46 | - grafana_agent_install 47 | - grafana_agent_configure 48 | - grafana_agent_run 49 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/preflight.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Preflight variable checks 3 | ansible.builtin.import_tasks: preflight/vars.yaml 4 | 5 | - name: Systemd variable checks 6 | ansible.builtin.import_tasks: preflight/systemd.yaml 7 | 8 | - name: Install variable checks 9 | ansible.builtin.import_tasks: preflight/install.yaml 10 | 11 | - name: Download variable checks 12 | ansible.builtin.import_tasks: preflight/download.yaml 13 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/preflight/download.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get Grafana Agent version from Github 3 | when: grafana_agent_version == 'latest' and not __grafana_agent_local_install 4 | block: 5 | - name: Get the latest published Grafana Agent # noqa command-instead-of-module 6 | ansible.builtin.shell: | 7 | curl -s https://api.github.com/repos/{{ _grafana_agent_github_org }}/{{ _grafana_agent_github_repo }}/releases/latest \ 8 | | grep -m 1 tag_name \ 9 | | cut -d '"' -f 4 | cut -c 2- 10 | changed_when: false 11 | run_once: true 12 | delegate_to: localhost 13 | become: false 14 | register: _grafana_agent_version_request 15 | 16 | - name: Fail if cannot get Grafana Agent Version 17 | ansible.builtin.fail: 18 | msg: Issue getting the Grafana Agent Version 19 | when: _grafana_agent_version_request == "" 20 | 21 | - name: Set the Grafana Agent version 22 | ansible.builtin.set_fact: 23 | grafana_agent_version: "{{ _grafana_agent_version_request.stdout }}" 24 | 25 | - name: Grafana Agent version to download 26 | ansible.builtin.debug: 27 | var: grafana_agent_version 28 | 29 | - name: Set the Grafana Agent download URL 30 | ansible.builtin.set_fact: 31 | _grafana_agent_download_url: |- 32 | {{ grafana_agent_base_download_url }}/v{{ grafana_agent_version }}/{{ _grafana_agent_download_archive_file }} 33 | 34 | - name: Grafana Agent download URL 35 | ansible.builtin.debug: 36 | var: _grafana_agent_download_url 37 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/preflight/install.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Default to non-local install 3 | ansible.builtin.set_fact: 4 | __grafana_agent_local_install: false 5 | 6 | - name: Fail when grafana_agent_local_binary_file is defined but the file doesn't exist 7 | when: grafana_agent_local_binary_file is defined and grafana_agent_local_binary_file | length > 0 8 | block: 9 | - name: Check if grafana_agent_local_binary_file exists 10 | ansible.builtin.stat: 11 | path: "{{ grafana_agent_local_binary_file }}" 12 | register: __grafana_agent_local_binary_check 13 | become: false 14 | delegate_to: localhost 15 | check_mode: false 16 | 17 | - name: Fail when the grafana_agent_local_binary_file does not exist 18 | ansible.builtin.fail: 19 | msg: "The grafana_agent_local_binary_file ({{ grafana_agent_local_binary_file }}) was specified but does not exist" 20 | when: not __grafana_agent_local_binary_check.stat.exists 21 | 22 | - name: Change to local install 23 | ansible.builtin.set_fact: 24 | __grafana_agent_local_install: true 25 | 26 | - name: Fail when grafana_agent_provisioned_config_file is defined but the file doesn't exist 27 | when: grafana_agent_provisioned_config_file is defined and grafana_agent_provisioned_config_file | length > 0 28 | block: 29 | - name: Check if grafana_agent_provisioned_config_file exists 30 | ansible.builtin.stat: 31 | path: "{{ grafana_agent_provisioned_config_file }}" 32 | register: __grafana_agent_provisioned_config_file_check 33 | become: false 34 | delegate_to: localhost 35 | check_mode: false 36 | 37 | - name: Fail when the grafana_agent_provisioned_config_file does not exist 38 | ansible.builtin.fail: 39 | msg: "The grafana_agent_provisioned_config_file ({{ grafana_agent_provisioned_config_file }}) was specified but does not exist" 40 | when: not __grafana_agent_provisioned_config_file_check.stat.exists 41 | 42 | - name: Check if grafana_agent is already installed on the host 43 | ansible.builtin.stat: 44 | path: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}" 45 | register: __grafana_agent_is_installed 46 | check_mode: false 47 | 48 | - name: Is Grafana Agent already installed on the host 49 | ansible.builtin.debug: 50 | var: __grafana_agent_is_installed.stat.exists 51 | 52 | - name: Install checks 53 | when: __grafana_agent_is_installed.stat.exists and not __grafana_agent_local_install 54 | block: 55 | - name: Gather currently installed grafana-agent version from the host 56 | ansible.builtin.shell: 57 | cmd: | 58 | {{ grafana_agent_install_dir }}/{{ grafana_agent_binary }} --version | \ 59 | head -n 1 | \ 60 | awk '{ print $3; }' | \ 61 | cut -d 'v' -f 2 62 | changed_when: false 63 | register: __grafana_agent_current_version_output 64 | check_mode: false 65 | 66 | - name: Set Grafana Agent installed version for the host 67 | ansible.builtin.set_fact: 68 | __grafana_agent_installed_version: "{{ __grafana_agent_current_version_output.stdout }}" 69 | 70 | - name: Grafana Agent installed version on host 71 | ansible.builtin.debug: 72 | var: __grafana_agent_installed_version 73 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/preflight/systemd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Assert usage of systemd as an init system 3 | ansible.builtin.assert: 4 | that: ansible_facts['service_mgr'] == 'systemd' 5 | msg: This role only works with systemd 6 | 7 | - name: Get systemd version # noqa command-instead-of-module 8 | ansible.builtin.command: systemctl --version 9 | changed_when: false 10 | check_mode: false 11 | register: __systemd_version 12 | 13 | - name: Set systemd version fact 14 | ansible.builtin.set_fact: 15 | grafana_agent_systemd_version: "{{ __systemd_version.stdout_lines[0] | regex_replace('^systemd\\s(\\d+).*$', '\\1') }}" 16 | 17 | - name: Fail when _grafana_agent_systemd_dir the directory doesn't exist 18 | block: 19 | - name: Check if _grafana_agent_systemd_dir exists 20 | ansible.builtin.stat: 21 | path: "{{ _grafana_agent_systemd_dir }}" 22 | register: ___grafana_agent_systemd_dir_check 23 | check_mode: false 24 | 25 | - name: Fail when the _grafana_agent_systemd_dir directory does not exist 26 | ansible.builtin.fail: 27 | msg: "The _grafana_agent_systemd_dir ({{ _grafana_agent_systemd_dir }}) does not exist" 28 | when: not ___grafana_agent_systemd_dir_check.stat.exists 29 | -------------------------------------------------------------------------------- /roles/grafana_agent/tasks/preflight/vars.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Performs initial variable validation 3 | - name: Fail when variables are not defined 4 | ansible.builtin.fail: 5 | msg: "The {{ item }} property must be set" 6 | when: ( vars[item] is not defined ) 7 | with_items: 8 | - grafana_agent_version 9 | - grafana_agent_install_dir 10 | - grafana_agent_binary 11 | - grafana_agent_config_dir 12 | - grafana_agent_config_filename 13 | - grafana_agent_env_file 14 | - grafana_agent_local_tmp_dir 15 | - grafana_agent_wal_dir 16 | - grafana_agent_positions_dir 17 | - grafana_agent_mode 18 | - _grafana_agent_systemd_dir 19 | - _grafana_agent_systemd_unit 20 | - grafana_agent_user 21 | - grafana_agent_user_group 22 | - grafana_agent_user_shell 23 | - grafana_agent_user_createhome 24 | - grafana_agent_local_binary_file 25 | - grafana_agent_flags_extra 26 | - grafana_agent_env_vars 27 | - grafana_agent_env_file_vars 28 | - grafana_agent_provisioned_config_file 29 | - grafana_agent_metrics_config 30 | - grafana_agent_logs_config 31 | - grafana_agent_traces_config 32 | - grafana_agent_integrations_config 33 | 34 | - name: Fail when variables do not have a length 35 | ansible.builtin.fail: 36 | msg: "The {{ item }} property must be valued" 37 | when: ( vars[item] | string | length == 0 ) 38 | with_items: 39 | - grafana_agent_version 40 | - grafana_agent_install_dir 41 | - grafana_agent_binary 42 | - grafana_agent_config_dir 43 | - grafana_agent_config_filename 44 | - grafana_agent_env_file 45 | - grafana_agent_local_tmp_dir 46 | - grafana_agent_wal_dir 47 | - grafana_agent_positions_dir 48 | - grafana_agent_mode 49 | - _grafana_agent_systemd_dir 50 | - _grafana_agent_systemd_unit 51 | - grafana_agent_user 52 | - grafana_agent_user_group 53 | - grafana_agent_user_shell 54 | - grafana_agent_user_createhome 55 | 56 | - name: Fail when variables are not a number 57 | ansible.builtin.fail: 58 | msg: "The {{ item }} property must be number" 59 | when: ( vars[item] is defined and vars[item] is not number) 60 | with_items: [] 61 | 62 | - name: Fail when flags are not a boolean 63 | ansible.builtin.fail: 64 | msg: "The {{ item }} property must be a boolean true or false" 65 | when: ( vars[item] | bool | string | lower ) not in ['true', 'false'] 66 | with_items: 67 | - grafana_agent_user_createhome 68 | 69 | - name: Fail when the agent mode is not "flow" or "static" 70 | ansible.builtin.fail: 71 | msg: "The grafana_agent_mode property must be a boolean 'flow' or 'static'" 72 | when: grafana_agent_mode not in ['flow', 'static'] 73 | -------------------------------------------------------------------------------- /roles/grafana_agent/templates/EnvironmentFile.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | # Grafana Agent Environment File 3 | AGENT_MODE={{ grafana_agent_mode }} 4 | 5 | GOMAXPROCS={{ ansible_facts['processor_vcpus']|default(ansible_facts['processor_count']) }} 6 | 7 | {% for key, value in grafana_agent_env_file_vars.items() %} 8 | {{key}}={{value}} 9 | {% endfor %} 10 | -------------------------------------------------------------------------------- /roles/grafana_agent/templates/config.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | {{ ansible_managed | comment }} 3 | 4 | ################################################################################################# 5 | # Configures the server of the Agent used to enable self-scraping # 6 | ################################################################################################# 7 | # Docs: https://grafana.com/docs/agent/latest/configuration/server-config/ 8 | server: 9 | {{ grafana_agent_server_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }} 10 | 11 | ################################################################################################# 12 | # Configures metric collection # 13 | ################################################################################################# 14 | # Docs: https://grafana.com/docs/agent/latest/configuration/metrics-config/ 15 | metrics: 16 | {{ grafana_agent_metrics_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }} 17 | 18 | ################################################################################################# 19 | # Configures logs collection # 20 | ################################################################################################# 21 | # Docs: https://grafana.com/docs/agent/latest/configuration/logs-config/ 22 | logs: 23 | {{ grafana_agent_logs_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }} 24 | 25 | {% if grafana_agent_mode == 'static' %} 26 | ################################################################################################# 27 | # Configures traces collection # 28 | ################################################################################################# 29 | # Docs: https://grafana.com/docs/agent/latest/configuration/traces-config/ 30 | traces: 31 | {{ grafana_agent_traces_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }} 32 | {% endif %} 33 | 34 | ################################################################################################# 35 | # Configures integrations for the agent # 36 | ################################################################################################# 37 | # Docs: https://grafana.com/docs/agent/latest/configuration/integrations/ 38 | integrations: 39 | {{ grafana_agent_integrations_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }} 40 | -------------------------------------------------------------------------------- /roles/grafana_agent/templates/grafana-agent.service.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | 3 | [Unit] 4 | Description=Grafana Agent 5 | Documentation=https://grafana.com/docs/agent/latest/ 6 | After=network-online.target 7 | 8 | [Service] 9 | Type=simple 10 | User={{ grafana_agent_user }} 11 | Group={{ grafana_agent_user_group }} 12 | WorkingDirectory={{ grafana_agent_data_dir }} 13 | {% for key, value in grafana_agent_env_vars.items() %} 14 | Environment={{key}}={{value}} 15 | {% endfor %} 16 | EnvironmentFile={{ grafana_agent_config_dir }}/{{ grafana_agent_env_file}} 17 | {% for key, value in grafana_agent_service_extra.items() %} 18 | {{ key }}={{ value }} 19 | {% endfor %} 20 | 21 | {% if grafana_agent_mode == 'flow' %} 22 | ExecStart={{ grafana_agent_install_dir }}/{{ grafana_agent_binary }} run \ 23 | {% for flag, flag_value in grafana_agent_flags_extra.items() %} 24 | {% if not flag_value %} 25 | --{{ flag }} \ 26 | {% elif flag_value is string %} 27 | --{{ flag }}={{ flag_value }} \ 28 | {% elif flag_value is sequence %} 29 | {% for flag_value_item in flag_value %} 30 | --{{ flag }}={{ flag_value_item }} \ 31 | {% endfor %} 32 | {% endif %} 33 | {% endfor %} 34 | {{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }} 35 | {% else %} 36 | ExecStart={{ grafana_agent_install_dir }}/{{ grafana_agent_binary }} \ 37 | {% for flag, flag_value in grafana_agent_flags_extra.items() %} 38 | {% if not flag_value %} 39 | --{{ flag }} \ 40 | {% elif flag_value is string %} 41 | --{{ flag }}={{ flag_value }} \ 42 | {% elif flag_value is sequence %} 43 | {% for flag_value_item in flag_value %} 44 | --{{ flag }}={{ flag_value_item }} \ 45 | {% endfor %} 46 | {% endif %} 47 | {% endfor %} 48 | --config.file={{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }} 49 | {% endif %} 50 | 51 | SyslogIdentifier=grafana-agent 52 | Restart=always 53 | 54 | {% if grafana_agent_systemd_version | int >= 232 %} 55 | ProtectSystem=strict 56 | ProtectControlGroups=true 57 | ProtectKernelModules=true 58 | ProtectKernelTunables=yes 59 | {% else %} 60 | ProtectSystem=full 61 | {% endif %} 62 | ReadWritePaths=/tmp {{ grafana_agent_data_dir }} {{ grafana_agent_positions_dir }} {{ grafana_agent_wal_dir }} 63 | 64 | [Install] 65 | WantedBy=multi-user.target 66 | -------------------------------------------------------------------------------- /roles/grafana_agent/vars/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | _grafana_agent_github_org: grafana 3 | _grafana_agent_github_repo: agent 4 | 5 | # set the go cpu arch 6 | _download_cpu_arch_map: 7 | i386: '386' 8 | x86_64: amd64 9 | aarch64: arm64 10 | armv7l: armv7 11 | armv6l: armv6 12 | 13 | _grafana_agent_cpu_arch: "{{ _download_cpu_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}" 14 | 15 | # set the go os family 16 | _grafana_agent_os_family: "{{ ansible_facts['system'] | lower }}" 17 | 18 | # set the name of the archive file to download 19 | _grafana_agent_download_archive_file: "grafana-agent-{{ _grafana_agent_os_family }}-{{ _grafana_agent_cpu_arch }}.zip" 20 | 21 | # set the name of the binary file 22 | _grafana_agent_download_binary_file: "grafana-agent-{{ _grafana_agent_os_family }}-{{ _grafana_agent_cpu_arch }}" 23 | 24 | # systemd info 25 | _grafana_agent_systemd_dir: /lib/systemd/system/ 26 | _grafana_agent_systemd_unit: grafana-agent.service 27 | 28 | # Server http address, used in self health check after start 29 | _grafana_agent_healthcheck_endpoint: "http://{{ grafana_agent_flags_extra['server.http.address'] if grafana_agent_flags_extra['server.http.address'] is defined else '127.0.0.1:12345' }}/-/ready" 30 | -------------------------------------------------------------------------------- /roles/loki/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for loki 3 | loki_version: "latest" 4 | loki_uninstall: false 5 | loki_http_listen_port: 3100 6 | loki_http_listen_address: "0.0.0.0" 7 | loki_expose_port: false 8 | loki_download_url_rpm: "https://github.com/grafana/loki/releases/download/v{{ loki_version }}/loki-{{ loki_version }}.{{ __loki_arch }}.rpm" 9 | loki_download_url_deb: "https://github.com/grafana/loki/releases/download/v{{ loki_version }}/loki_{{ loki_version }}_{{ __loki_arch }}.deb" 10 | loki_working_path: "/var/lib/loki" 11 | loki_ruler_alert_path: "{{ loki_working_path }}/rules/fake" 12 | 13 | # Default Variables for /etc/loki/config.yml 14 | loki_auth_enabled: false 15 | loki_target: "all" 16 | loki_ballast_bytes: 0 17 | 18 | loki_server: 19 | http_listen_address: "{{ loki_http_listen_address }}" 20 | http_listen_port: "{{ loki_http_listen_port }}" 21 | grpc_listen_port: 9096 22 | 23 | loki_common: 24 | instance_addr: 127.0.0.1 25 | path_prefix: "{{ loki_working_path }}" 26 | storage: 27 | filesystem: 28 | chunks_directory: "{{ loki_working_path }}/chunks" 29 | rules_directory: "{{ loki_working_path }}/rules" 30 | replication_factor: 1 31 | ring: 32 | kvstore: 33 | store: inmemory 34 | 35 | loki_query_range: 36 | results_cache: 37 | cache: 38 | embedded_cache: 39 | enabled: true 40 | max_size_mb: 100 41 | 42 | loki_schema_config: 43 | configs: 44 | - from: 2020-10-24 45 | store: tsdb 46 | object_store: filesystem 47 | schema: v13 48 | index: 49 | prefix: index_ 50 | period: 24h 51 | 52 | loki_ruler: 53 | storage: 54 | type: local 55 | local: 56 | directory: "{{ loki_working_path }}/rules" 57 | rule_path: "{{ loki_working_path }}/rules_tmp" 58 | ring: 59 | kvstore: 60 | store: inmemory 61 | enable_api: true 62 | enable_alertmanager_v2: true 63 | alertmanager_url: http://localhost:9093 64 | 65 | loki_analytics: 66 | reporting_enabled: false 67 | 68 | # Alerting Rules Variables 69 | # loki_ruler_alerts: 70 | # - name: Logs.Nextcloud 71 | # rules: 72 | # - alert: NextcloudLoginFailed 73 | # expr: | 74 | # count by (filename,env,job) (count_over_time({job=~"nextcloud"} | json | message=~"Login failed.*" [10m])) > 4 75 | # for: 0m 76 | # labels: 77 | # severity: critical 78 | # annotations: 79 | # summary: "{% raw %}On {{ $labels.job }} in log {{ $labels.filename }} failed login detected.{% endraw %}" 80 | # - name: Logs.sshd 81 | # rules: 82 | # - alert: SshLoginFailed 83 | # expr: | 84 | # count_over_time({job=~"secure"} |="sshd[" |~": Failed|: Invalid|: Connection closed by authenticating user" | __error__="" [15m]) > 15 85 | # for: 0m 86 | # labels: 87 | # severity: critical 88 | # annotations: 89 | # summary: "{% raw %}SSH authentication failure (instance {{ $labels.instance }}).{% endraw %}" 90 | 91 | # Additional Config Variables for /etc/loki/config.yml 92 | # loki_distributor: 93 | # loki_querier: 94 | # loki_query_scheduler: 95 | # loki_frontend: 96 | # loki_ingester_client: 97 | # loki_ingester: 98 | # loki_index_gateway: 99 | # loki_storage_config: 100 | # loki_chunk_store_config: 101 | # loki_compactor: 102 | # loki_limits_config: 103 | # loki_frontend_worker: 104 | # loki_table_manager: 105 | # loki_memberlist: 106 | # loki_runtime_config: 107 | # loki_operational_config: 108 | # loki_tracing: 109 | # loki_bloom_build: 110 | # loki_bloom_gateway: 111 | -------------------------------------------------------------------------------- /roles/loki/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for loki 3 | - name: Restart loki 4 | listen: "restart loki" 5 | ansible.builtin.systemd: 6 | daemon_reload: true 7 | name: loki.service 8 | state: restarted 9 | enabled: true 10 | when: not ansible_check_mode 11 | -------------------------------------------------------------------------------- /roles/loki/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: loki 4 | author: voidquark 5 | description: Manage Grafana Loki Application 6 | license: "GPL-3.0-or-later" 7 | min_ansible_version: "2.10" 8 | platforms: 9 | - name: EL 10 | versions: 11 | - "8" 12 | - "9" 13 | - name: Fedora 14 | versions: 15 | - all 16 | - name: Debian 17 | versions: 18 | - all 19 | - name: Ubuntu 20 | versions: 21 | - all 22 | galaxy_tags: 23 | - loki 24 | - grafana 25 | - logging 26 | - monitoring 27 | 28 | dependencies: [] 29 | -------------------------------------------------------------------------------- /roles/loki/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | roles: 5 | - role: grafana.grafana.loki 6 | -------------------------------------------------------------------------------- /roles/loki/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | ignore-errors: true 6 | driver: 7 | name: docker 8 | platforms: 9 | - name: instance 10 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest" 11 | command: ${MOLECULE_DOCKER_COMMAND:-""} 12 | volumes: 13 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 14 | cgroupns_mode: host 15 | privileged: true 16 | pre_build_image: true 17 | provisioner: 18 | name: ansible 19 | playbooks: 20 | converge: converge.yml 21 | -------------------------------------------------------------------------------- /roles/loki/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for loki 3 | - name: Include OS specific variables 4 | ansible.builtin.include_vars: 5 | file: "{{ ansible_facts['os_family'] }}.yml" 6 | 7 | - name: Deploy Loki service 8 | ansible.builtin.include_tasks: 9 | file: "deploy.yml" 10 | when: not loki_uninstall 11 | 12 | - name: Uninstall Loki service 13 | ansible.builtin.include_tasks: 14 | file: "uninstall.yml" 15 | when: loki_uninstall 16 | -------------------------------------------------------------------------------- /roles/loki/tasks/setup-Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: APT - Install Loki 3 | ansible.builtin.apt: 4 | deb: "{{ loki_download_url_deb }}" 5 | state: present 6 | notify: restart loki 7 | when: __current_deployed_version.stdout is not defined or loki_version not in __current_deployed_version.stdout 8 | -------------------------------------------------------------------------------- /roles/loki/tasks/setup-RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: DNF - Install Loki from remote URL 3 | ansible.builtin.dnf: 4 | name: "{{ loki_download_url_rpm }}" 5 | state: present 6 | disable_gpg_check: true 7 | notify: restart loki 8 | when: __current_deployed_version.stdout is not defined or loki_version not in __current_deployed_version.stdout 9 | -------------------------------------------------------------------------------- /roles/loki/tasks/uninstall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for loki uninstall 3 | 4 | - name: Stop Loki service 5 | ansible.builtin.systemd: # noqa ignore-errors 6 | name: loki 7 | state: stopped 8 | ignore_errors: true 9 | 10 | - name: Uninstall Loki rpm package 11 | ansible.builtin.dnf: 12 | name: "loki" 13 | state: absent 14 | autoremove: true 15 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 16 | 17 | - name: Uninstall Loki deb package 18 | ansible.builtin.apt: 19 | name: "loki" 20 | state: absent 21 | purge: true 22 | when: ansible_facts['os_family'] == 'Debian' 23 | 24 | - name: Ensure that Loki firewalld rule is not present - tcp port {{ loki_http_listen_port }} 25 | ansible.posix.firewalld: # noqa ignore-errors 26 | immediate: true 27 | permanent: true 28 | port: "{{ loki_http_listen_port }}/tcp" 29 | state: disabled 30 | ignore_errors: true 31 | 32 | - name: Remove Loki directories" 33 | ansible.builtin.file: 34 | path: "{{ remove_me }}" 35 | state: absent 36 | loop: 37 | - "/etc/loki" 38 | - "{{ loki_working_path }}" 39 | loop_control: 40 | loop_var: remove_me 41 | 42 | - name: Remove the Loki system user 43 | ansible.builtin.user: 44 | name: "loki" 45 | force: true 46 | state: absent 47 | 48 | - name: Remove Loki system group 49 | ansible.builtin.group: 50 | name: "loki" 51 | state: absent 52 | -------------------------------------------------------------------------------- /roles/loki/templates/rules.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | groups: 3 | {{ loki_ruler_alerts | to_nice_yaml(indent=2,sort_keys=False) | indent(2,False) }} 4 | -------------------------------------------------------------------------------- /roles/loki/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __loki_arch_map: 3 | x86_64: 'amd64' 4 | armv6l: 'arm' 5 | armv7l: 'arm' 6 | aarch64: 'arm64' 7 | 8 | __loki_arch: "{{ __loki_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}" 9 | -------------------------------------------------------------------------------- /roles/loki/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __loki_arch: "{{ ansible_facts['architecture'] }}" 3 | -------------------------------------------------------------------------------- /roles/mimir/README.md: -------------------------------------------------------------------------------- 1 | # ansible-role-mimir 2 | Grafana Mimir 3 | ========= 4 | 5 | This role installs and configures a Mimir standalone application. 6 | 7 | ## Testing with Molecule 8 | To be able to test this collection locally, we use Molecule. Molecule is an Ansible test tool that enable us to run our roles inside containers. In our case, we are using Podman as a container runtime. To be able to run the Molecule test, you need to have the following installed on your machine: 9 | 10 | - Podman 11 | - Ansible 12 | - Python3 13 | 14 | ### First Time Setup 15 | To install all the dependencies, use the following commands: 16 | 17 | ```sh 18 | # Create a virtual environment 19 | python -m venv .venv 20 | 21 | # On MacOS, WSL, Linux 22 | source .venv/bin/activate 23 | 24 | # On Windows 25 | .\.venv\Scripts\activate 26 | 27 | # Install dependencies 28 | pip3 install ansible-core==2.16 'molecule-plugins[docker]' pytest-testinfra jmespath selinux passlib 29 | 30 | # Create molecule network 31 | docker network create molecule 32 | ``` 33 | 34 | ### Run Minio for local S3 35 | To be able to run Mimir using an object store backend, run the following command 36 | 37 | ```sh 38 | docker run -d \ 39 | -p 9000:9000 \ 40 | -p 9001:9001 \ 41 | --name minio-mimir \ 42 | --network molecule \ 43 | -e "MINIO_ROOT_USER=testtest" \ 44 | -e "MINIO_ROOT_PASSWORD=testtest" \ 45 | -e "MINIO_DEFAULT_BUCKETS=mimir" \ 46 | bitnami/minio:latest 47 | ``` 48 | 49 | ### Testing the changes 50 | To test the changes in a role run: 51 | ```sh 52 | molecule converge 53 | ## example: molecule converge 54 | ``` 55 | When Ansible has succesfully ran, you can run assertions against your infrastructure using. 56 | ```sh 57 | molecule verify 58 | ## example: `molecule verify` 59 | ``` 60 | 61 | You can also run commands like `molecule destroy`, `molecule prepare`, and `molecule test`. See Molecule documentation for more information 62 | 63 | ## Role Variables 64 | -------------- 65 | | Name | Type | Default | Description | 66 | |---|---|---|---| 67 | mimir_working_path|str|/usr/share/mimir|Used to specify the directory path where Mimir, a component of the Grafana Agent, stores its working files and temporary data.| 68 | mimir_uninstall|bool|false|If set to `true` will perfom uninstall instead of deployment.| 69 | mimir_ruler_alert_path|str|/data/ruler|Used to specify the directory path where the Mimir ruler component of the Grafana Agent stores its alert files.| 70 | mimir_http_listen_port|str|8080|Used to specify the port number on which the Mimir component of the Grafana Agent listens for incoming HTTP requests.| 71 | mimir_http_listen_address|str|0.0.0.0|Used to specify the network address on which the Mimir component of the Grafana Agent listens for incoming HTTP requests.| 72 | mimir_ruler.rule_path|str|/data/ruler|Used to specify the directory path where the Mimir ruler component of the Grafana Agent looks for rule files.| 73 | mimir_ruler.alertmanager_url|str|http://127.0.0.1:8080/alertmanager|Used to specify the URL or address of the Alertmanager API that the Mimir ruler component of the Grafana Agent should communicate with.| 74 | mimir_ruler.ring.heartbeat_period|str|2s|Used to specify the interval at which the Mimir ruler component of the Grafana Agent sends heartbeat signals to the ring.| 75 | mimir_ruler.heartbeat_timeout|str|10s|Used to specify the maximum duration of time that the Mimir ruler component of the Grafana Agent will wait for a heartbeat signal from other components in the ring.| 76 | mimir_alertmanager.data_dir|str|/data/alertmanager|sed to specify the directory path where the Mimir Alertmanager component of the Grafana Agent stores its data files.| 77 | mimir_alertmanager.fallback_config_file|str|/etc/alertmanager-fallback-config.yaml|Used to specify the path to a fallback configuration file for the Mimir Alertmanager component of the Grafana Agent.| 78 | mimir_alertmanager.external_url|str|http://localhost:9009/alertmanager|Used to specify the external URL or address at which the Mimir Alertmanager component of the Grafana Agent can be accessed.| 79 | mimir_server.log_level|str|warn|Used to specify the log level of the server. Possible configurations error, warn, info, debug| 80 | mimir_memberlist.join_members|[]| List of members for the Mimir cluster| 81 | -------------------------------------------------------------------------------- /roles/mimir/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for mimir 3 | mimir_version: "latest" 4 | mimir_uninstall: false 5 | __mimir_arch: "{{ arch_mapping[ansible_facts['architecture']] | default('amd64') }}" 6 | mimir_download_url_rpm: "https://github.com/grafana/mimir/releases/download/{{ mimir_version }}/{{ mimir_version }}_{{ __mimir_arch }}.rpm" 7 | mimir_download_url_deb: "https://github.com/grafana/mimir/releases/download/{{ mimir_version }}/{{ mimir_version }}_{{ __mimir_arch }}.deb" 8 | mimir_working_path: "/var/lib/mimir" 9 | mimir_ruler_alert_path: "{{ mimir_working_path }}/ruler" 10 | mimir_http_listen_port: 8080 11 | mimir_http_listen_address: "0.0.0.0" 12 | mimir_log_level: warn 13 | 14 | arch_mapping: 15 | x86_64: amd64 16 | aarch64: arm64 17 | armv7l: armhf 18 | i386: i386 19 | ppc64le: ppc64le 20 | 21 | mimir_ruler: 22 | rule_path: "{{ mimir_working_path }}/ruler" 23 | alertmanager_url: "http://127.0.0.1:{{ mimir_http_listen_port }}/alertmanager" 24 | ring: 25 | heartbeat_period: 5s 26 | heartbeat_timeout: 10s 27 | 28 | mimir_alertmanager: 29 | data_dir: "{{ mimir_working_path }}/alertmanager" 30 | external_url: "http://localhost:{{ mimir_http_listen_port }}/alertmanager" 31 | 32 | mimir_server: 33 | http_listen_port: "{{ mimir_http_listen_port }}" 34 | http_listen_address: "{{ mimir_http_listen_address }}" 35 | log_level: "{{ mimir_log_level }}" 36 | 37 | mimir_limits: 38 | compactor_blocks_retention_period: 6m 39 | -------------------------------------------------------------------------------- /roles/mimir/files/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-ansible-collection/ba88435e3944b0da572062509ad63dc48d05a863/roles/mimir/files/.gitkeep -------------------------------------------------------------------------------- /roles/mimir/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Restart mimir 4 | ansible.builtin.systemd: 5 | name: mimir.service 6 | state: restarted 7 | -------------------------------------------------------------------------------- /roles/mimir/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # meta/main.yml 3 | galaxy_info: 4 | author: "Grafana" 5 | role_name: mimir 6 | description: "Grafana - platform for analytics and monitoring" 7 | license: "MIT" 8 | min_ansible_version: "2.9" 9 | platforms: 10 | - name: Ubuntu 11 | versions: 12 | - bionic 13 | - xenial 14 | - name: Debian 15 | versions: 16 | - stretch 17 | - buster 18 | - name: EL 19 | versions: 20 | - "7" 21 | - "8" 22 | - name: Fedora 23 | versions: 24 | - "30" 25 | - "31" 26 | galaxy_tags: 27 | - grafana 28 | - dashboard 29 | - alerts 30 | - alerting 31 | - presentation 32 | - monitoring 33 | - metrics 34 | 35 | dependencies: [] 36 | 37 | allow_duplicates: true 38 | -------------------------------------------------------------------------------- /roles/mimir/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge mimir 3 | hosts: all 4 | collections: 5 | - grafana.grafana 6 | vars: 7 | mimir_storage: 8 | storage: 9 | backend: s3 10 | s3: 11 | endpoint: minio-mimir.:9000 12 | access_key_id: testtest 13 | secret_access_key: testtest 14 | insecure: true 15 | bucket_name: mimir 16 | 17 | # Blocks storage requires a prefix when using a common object storage bucket. 18 | mimir_blocks_storage: 19 | storage_prefix: blocks 20 | tsdb: 21 | dir: "{{ mimir_working_path}}/ingester" 22 | 23 | # Use memberlist, a gossip-based protocol, to enable the 3 Mimir replicas to communicate 24 | mimir_memberlist: 25 | join_members: 26 | - molecule-grafana-mimir01.:7946 27 | - molecule-grafana-mimir02.:7946 28 | - molecule-grafana-mimir03.:7946 29 | 30 | tasks: 31 | - name: "Run Grafana mimir collection" 32 | include_role: 33 | name: mimir 34 | -------------------------------------------------------------------------------- /roles/mimir/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: docker 6 | platforms: 7 | - name: molecule-grafana-mimir01 8 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest" 9 | command: ${MOLECULE_DOCKER_COMMAND:-"/sbin/init"} 10 | volumes: 11 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 12 | cgroupns_mode: host 13 | privileged: true 14 | pre_build_image: true 15 | network: molecule 16 | network_mode: "molecule" 17 | published_ports: 18 | - 7946 19 | - 9019:9009 20 | - 9096 21 | - name: molecule-grafana-mimir02 22 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest" 23 | command: ${MOLECULE_DOCKER_COMMAND:-"/sbin/init"} 24 | volumes: 25 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 26 | cgroupns_mode: host 27 | privileged: true 28 | pre_build_image: true 29 | network: molecule 30 | network_mode: "molecule" 31 | published_ports: 32 | - 7946 33 | - 9029:9009 34 | - 9096 35 | - name: molecule-grafana-mimir03 36 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest" 37 | command: ${MOLECULE_DOCKER_COMMAND:-"/sbin/init"} 38 | volumes: 39 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 40 | cgroupns_mode: host 41 | privileged: true 42 | pre_build_image: true 43 | network: molecule 44 | network_mode: "molecule" 45 | published_ports: 46 | - 7946 47 | - 9039:9009 48 | - 9096 49 | 50 | provisioner: 51 | name: ansible 52 | env: 53 | ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles 54 | 55 | verifier: 56 | name: testinfra 57 | lint: | 58 | set -e 59 | yamllint . 60 | ansible-lint . 61 | -------------------------------------------------------------------------------- /roles/mimir/molecule/default/tests/test_default.py: -------------------------------------------------------------------------------- 1 | from __future__ import (absolute_import, division, print_function) 2 | __metaclass__ = type 3 | 4 | import os 5 | import testinfra.utils.ansible_runner 6 | 7 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 8 | os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') 9 | 10 | 11 | def test_directories(host): 12 | dirs = [ 13 | "/etc/mimir", 14 | "/var/lib/mimir", 15 | ] 16 | files = [ 17 | "/etc/mimir/config.yml" 18 | ] 19 | for directory in dirs: 20 | d = host.file(directory) 21 | assert d.is_directory 22 | assert d.exists 23 | for file in files: 24 | f = host.file(file) 25 | assert f.exists 26 | assert f.is_file 27 | 28 | 29 | def test_service(host): 30 | s = host.service("mimir") 31 | assert s.is_running 32 | 33 | 34 | def test_packages(host): 35 | p = host.package("mimir") 36 | assert p.is_installed 37 | -------------------------------------------------------------------------------- /roles/mimir/tasks/deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for Mimir deployment 3 | - name: Obtain the latest version from the Mimir GitHub repo 4 | when: mimir_version == "latest" 5 | block: 6 | - name: Scrape Github API endpoint to obtain latest Mimir version 7 | ansible.builtin.uri: 8 | url: "https://api.github.com/repos/grafana/mimir/releases/latest" 9 | method: GET 10 | body_format: json 11 | become: false 12 | delegate_to: localhost 13 | run_once: true 14 | register: __github_latest_version 15 | 16 | - name: Latest available Mimir version 17 | ansible.builtin.set_fact: 18 | mimir_version: "{{ __github_latest_version.json.tag_name | regex_replace('^v?(\\d+\\.\\d+\\.\\d+)$', '\\1') }}" 19 | 20 | - name: Verify current deployed version 21 | block: 22 | - name: Check if Mimir binary is present 23 | ansible.builtin.stat: 24 | path: "/usr/bin/mimir" 25 | register: __already_deployed 26 | 27 | - name: Obtain current deployed Mimir version 28 | ansible.builtin.command: 29 | cmd: "/usr/bin/mimir --version" 30 | changed_when: false 31 | register: __current_deployed_version 32 | when: __already_deployed.stat.exists | bool 33 | 34 | - name: Include RedHat/Rocky setup 35 | ansible.builtin.include_tasks: 36 | file: setup-Redhat.yml 37 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 38 | 39 | - name: Include Debian/Ubuntu setup 40 | ansible.builtin.include_tasks: 41 | file: setup-Debian.yml 42 | when: ansible_facts['os_family'] == 'Debian' 43 | 44 | - name: Check if Mimir default dir is present 45 | ansible.builtin.stat: 46 | path: "/tmp/mimir/boltdb-shipper-active" 47 | register: __default_structure 48 | 49 | - name: Default structure cleanup 50 | when: __default_structure.stat.exists | bool 51 | block: 52 | - name: Ensure that Mimir is stopped before default cleanup 53 | ansible.builtin.systemd: 54 | name: mimir.service 55 | state: stopped 56 | 57 | - name: Remove default configuration from "/tmp/mimir" directory 58 | ansible.builtin.file: 59 | path: "/tmp/mimir" 60 | state: absent 61 | 62 | - name: Ensure that Mimir working path exists 63 | ansible.builtin.file: 64 | path: "{{ mimir_working_path }}" 65 | state: directory 66 | owner: "mimir" 67 | group: "mimir" 68 | mode: "0755" 69 | 70 | - name: Ensure that Mimir rule path exists 71 | ansible.builtin.file: 72 | path: "{{ mimir_ruler_alert_path }}" 73 | state: directory 74 | owner: "mimir" 75 | group: "mimir" 76 | mode: "0755" 77 | when: 78 | - mimir_ruler_alert_path is defined 79 | - mimir_ruler is defined 80 | 81 | - name: Template Mimir config - /etc/mimir/config.yml 82 | ansible.builtin.template: 83 | src: "config.yml.j2" 84 | dest: "/etc/mimir/config.yml" 85 | owner: "mimir" 86 | group: "mimir" 87 | mode: "0644" 88 | validate: "mimir -modules --config.file=%s" 89 | notify: 90 | - Restart mimir 91 | 92 | - name: Ensure restart has completed 93 | ansible.builtin.meta: flush_handlers 94 | 95 | - name: Ensure that Mimir is started 96 | ansible.builtin.systemd: 97 | name: mimir.service 98 | state: started 99 | enabled: true 100 | 101 | 102 | - name: Verify that Mimir URL is responding 103 | ansible.builtin.uri: 104 | url: "http://{{ mimir_http_listen_address }}:{{ mimir_http_listen_port }}/ready" 105 | method: GET 106 | register: mimir_verify_url_status_code 107 | retries: 5 108 | delay: 8 109 | until: mimir_verify_url_status_code.status == 200 110 | -------------------------------------------------------------------------------- /roles/mimir/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Deploy Mimir service 2 | ansible.builtin.include_tasks: 3 | file: "deploy.yml" 4 | when: not mimir_uninstall 5 | 6 | - name: Uninstall Mimir service 7 | ansible.builtin.include_tasks: 8 | file: "uninstall.yml" 9 | when: mimir_uninstall 10 | -------------------------------------------------------------------------------- /roles/mimir/tasks/setup-Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: APT - Install Mimir 3 | ansible.builtin.apt: 4 | deb: "{{ mimir_download_url_deb }}" 5 | state: present 6 | notify: Restart mimir 7 | when: __current_deployed_version.stdout is not defined or mimir_version not in __current_deployed_version.stdout 8 | -------------------------------------------------------------------------------- /roles/mimir/tasks/setup-Redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: DNF - Install Mimir from remote URL 3 | ansible.builtin.dnf: 4 | name: "{{ mimir_download_url_rpm }}" 5 | state: present 6 | disable_gpg_check: true 7 | notify: Restart mimir 8 | when: __current_deployed_version.stdout is not defined or mimir_version not in __current_deployed_version.stdout 9 | -------------------------------------------------------------------------------- /roles/mimir/tasks/uninstall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for Mimir uninstall 3 | 4 | - name: Stop Mimir service 5 | ansible.builtin.systemd: # noqa ignore-errors 6 | name: mimir 7 | state: stopped 8 | ignore_errors: true 9 | 10 | - name: Uninstall Mimir rpm package 11 | ansible.builtin.dnf: 12 | name: "mimir" 13 | state: absent 14 | autoremove: true 15 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 16 | 17 | - name: Uninstall Mimir deb package 18 | ansible.builtin.apt: 19 | name: "mimir" 20 | state: absent 21 | purge: true 22 | when: ansible_facts['os_family'] == 'Debian' 23 | 24 | - name: Ensure that Mimir firewalld rule is not present - tcp port {{ mimir_http_listen_port }} 25 | ansible.posix.firewalld: # noqa ignore-errors 26 | immediate: true 27 | permanent: true 28 | port: "{{ mimir_http_listen_port }}/tcp" 29 | state: disabled 30 | ignore_errors: true 31 | 32 | - name: Remove Mimir directories" 33 | ansible.builtin.file: 34 | path: "{{ remove_me }}" 35 | state: absent 36 | loop: 37 | - "/etc/mimir" 38 | - "{{ mimir_working_path }}" 39 | loop_control: 40 | loop_var: remove_me 41 | 42 | - name: Remove the Mimir system user 43 | ansible.builtin.user: 44 | name: "mimir" 45 | force: true 46 | state: absent 47 | 48 | - name: Remove Mimir system group 49 | ansible.builtin.group: 50 | name: "mimir" 51 | state: absent 52 | -------------------------------------------------------------------------------- /roles/mimir/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-ansible-collection/ba88435e3944b0da572062509ad63dc48d05a863/roles/mimir/templates/.gitkeep -------------------------------------------------------------------------------- /roles/mimir/templates/config.yml.j2: -------------------------------------------------------------------------------- 1 | # Run Mimir in single process mode, with all components running in 1 process. 2 | target: all,alertmanager,overrides-exporter 3 | 4 | # Configure Mimir to use Minio as object storage backend. 5 | common: 6 | {% if mimir_storage is defined %} 7 | {{ mimir_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 8 | {% endif %} 9 | 10 | # Blocks storage requires a prefix when using a common object storage bucket. 11 | {% if mimir_blocks_storage is defined %} 12 | blocks_storage: 13 | {{ mimir_blocks_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 14 | {% endif %} 15 | 16 | # Blocks storage requires a prefix when using a common object storage bucket. 17 | {% if mimir_ruler_storage is defined %} 18 | ruler_storage: 19 | {{ mimir_ruler_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 20 | {% endif %} 21 | 22 | # Alertmanager storage requires a prefix when using a common object storage bucket. 23 | {% if mimir_alertmanager_storage is defined %} 24 | alertmanager_storage: 25 | {{ mimir_alertmanager_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 26 | {% endif %} 27 | 28 | # Use memberlist, a gossip-based protocol, to enable the 3 Mimir replicas to communicate. 29 | {% if mimir_memberlist is defined %} 30 | memberlist: 31 | {{ mimir_memberlist | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 32 | {% endif %} 33 | 34 | {% if mimir_ruler is defined %} 35 | ruler: 36 | {{ mimir_ruler | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 37 | {% endif %} 38 | 39 | {% if mimir_alertmanager is defined %} 40 | alertmanager: 41 | {{ mimir_alertmanager | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 42 | {% endif %} 43 | 44 | {% if mimir_server is defined %} 45 | server: 46 | {{ mimir_server | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 47 | {% endif %} 48 | 49 | {% if mimir_limits is defined %} 50 | limits: 51 | {{ mimir_limits | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 52 | {% endif %} 53 | 54 | {% if mimir_distributor is defined %} 55 | distributor: 56 | {{ mimir_distributor | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 57 | {% endif %} 58 | -------------------------------------------------------------------------------- /roles/mimir/vars/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-ansible-collection/ba88435e3944b0da572062509ad63dc48d05a863/roles/mimir/vars/.gitkeep -------------------------------------------------------------------------------- /roles/opentelemetry_collector/defaults/main.yml: -------------------------------------------------------------------------------- 1 | otel_collector_version: "0.90.1" 2 | 3 | otel_collector_binary_url: "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v{{ otel_collector_version }}/{% if otel_collector_type == 'contrib' %}otelcol-contrib_{{ otel_collector_version }}_linux_{{ otel_collector_arch }}{% else %}otelcol_{{ otel_collector_version }}_linux_{{ otel_collector_arch }}{% endif %}.tar.gz" 4 | 5 | otel_collector_latest_url: 'https://api.github.com/repos/open-telemetry/opentelemetry-collector-releases/releases/latest' 6 | 7 | arch_mapping: 8 | x86_64: amd64 9 | aarch64: arm64 10 | armv7l: armhf 11 | i386: i386 12 | ppc64le: ppc64le 13 | 14 | otel_collector_arch: "{{ arch_mapping[ansible_facts['architecture']] | default('amd64') }}" 15 | otel_collector_service_name: "otel-collector" 16 | otel_collector_type: contrib 17 | otel_collector_executable: "{% if otel_collector_type == 'contrib' %}otelcol-contrib{% else %}otelcol{% endif %}" 18 | otel_collector_installation_dir: "/etc/otel-collector" 19 | otel_collector_config_dir: "/etc/otel-collector" 20 | otel_collector_config_file: "config.yaml" 21 | otel_collector_service_user: "otel" 22 | otel_collector_service_group: "otel" 23 | otel_collector_service_statedirectory: "otel-collector" 24 | 25 | otel_collector_receivers: "" 26 | otel_collector_exporters: "" 27 | otel_collector_processors: "" 28 | otel_collector_extensions: "" 29 | otel_collector_service: "" 30 | otel_collector_connectors: "" 31 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: Restart OpenTelemetry Collector 2 | ansible.builtin.systemd: 3 | name: "{{ otel_collector_service_name }}" 4 | state: restarted 5 | become: true 6 | ignore_errors: '{{ ansible_check_mode | bool }}' 7 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Ishan Jain 3 | description: Role to install and configure OpenTelemetry Collector 4 | license: "GPL-3.0-or-later" 5 | min_ansible_version: "2.11" 6 | platforms: 7 | - name: Fedora 8 | versions: 9 | - "all" 10 | - name: Debian 11 | versions: 12 | - "all" 13 | - name: Ubuntu 14 | versions: 15 | - "all" 16 | - name: EL 17 | versions: 18 | - "all" 19 | galaxy_tags: 20 | - grafana 21 | - observability 22 | - monitoring 23 | - opentelemetry 24 | - telemetry 25 | - collector 26 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/default-check-first/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Run role" 3 | hosts: all 4 | any_errors_fatal: true 5 | roles: 6 | - grafana.grafana.opentelemetry_collector 7 | vars: 8 | otel_collector_receivers: 9 | hostmetrics: 10 | scrapers: 11 | cpu: 12 | memory: 13 | otel_collector_processors: 14 | memory_limiter: 15 | check_interval: 2s 16 | limit_percentage: 80 17 | spike_limit_percentage: 25 18 | batch: 19 | otel_collector_exporters: 20 | prometheus: 21 | endpoint: '127.0.0.1:9999' 22 | otel_collector_service: 23 | pipelines: 24 | metrics: 25 | receivers: '{{ otel_collector_receivers.keys() }}' 26 | processors: '{{ otel_collector_processors.keys() }}' 27 | exporters: '{{ otel_collector_exporters.keys() }}' 28 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/default-check-first/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This scenario verifies that we can run ansible on an unprovisioned machine in check-mode 3 | scenario: 4 | name: default-check-first 5 | test_sequence: 6 | - dependency 7 | - cleanup 8 | - destroy 9 | - create 10 | - prepare 11 | - check 12 | - cleanup 13 | - destroy 14 | 15 | platforms: 16 | - name: almalinux-9 17 | image: dokken/almalinux-9 18 | pre_build_image: true 19 | privileged: true 20 | cgroup_parent: docker.slice 21 | command: /lib/systemd/systemd 22 | - name: centos-stream-9 23 | image: dokken/centos-stream-9 24 | pre_build_image: true 25 | privileged: true 26 | cgroup_parent: docker.slice 27 | command: /lib/systemd/systemd 28 | - name: debian-11 29 | image: dokken/debian-11 30 | pre_build_image: true 31 | privileged: true 32 | cgroup_parent: docker.slice 33 | command: /lib/systemd/systemd 34 | - name: fedora-36 35 | image: dokken/fedora-36 36 | pre_build_image: true 37 | privileged: true 38 | cgroup_parent: docker.slice 39 | command: /lib/systemd/systemd 40 | - name: fedora-37 41 | image: dokken/fedora-37 42 | pre_build_image: true 43 | privileged: true 44 | cgroup_parent: docker.slice 45 | command: /lib/systemd/systemd 46 | - name: ubuntu-20.04 47 | image: dokken/ubuntu-20.04 48 | pre_build_image: true 49 | privileged: true 50 | cgroup_parent: docker.slice 51 | command: /lib/systemd/systemd 52 | - name: ubuntu-22.04 53 | image: dokken/ubuntu-22.04 54 | pre_build_image: true 55 | privileged: true 56 | cgroup_parent: docker.slice 57 | command: /lib/systemd/systemd 58 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/default-check-first/tests/test_default.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __metaclass__ = type 4 | 5 | import os 6 | import testinfra.utils.ansible_runner 7 | 8 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 9 | os.environ["MOLECULE_INVENTORY_FILE"] 10 | ).get_hosts("all") 11 | 12 | 13 | def test_directories(host): 14 | dirs = [ 15 | "/etc/otel-collector", 16 | ] 17 | files = ["/etc/otel-collector/config.yaml"] 18 | for directory in dirs: 19 | d = host.file(directory) 20 | assert d.is_directory 21 | assert d.exists 22 | for file in files: 23 | f = host.file(file) 24 | assert f.exists 25 | assert f.is_file 26 | 27 | 28 | def test_service(host): 29 | s = host.service("otel-collector") 30 | # assert s.is_enabled 31 | assert s.is_running 32 | 33 | 34 | def test_socket(host): 35 | assert host.socket("tcp://127.0.0.1:9999").is_listening 36 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Run role" 3 | hosts: all 4 | any_errors_fatal: true 5 | roles: 6 | - grafana.grafana.opentelemetry_collector 7 | vars: 8 | otel_collector_receivers: 9 | hostmetrics: 10 | scrapers: 11 | cpu: 12 | memory: 13 | otel_collector_processors: 14 | memory_limiter: 15 | check_interval: 2s 16 | limit_percentage: 80 17 | spike_limit_percentage: 25 18 | batch: 19 | otel_collector_exporters: 20 | prometheus: 21 | endpoint: '127.0.0.1:9999' 22 | otel_collector_service: 23 | pipelines: 24 | metrics: 25 | receivers: '{{ otel_collector_receivers.keys() }}' 26 | processors: '{{ otel_collector_processors.keys() }}' 27 | exporters: '{{ otel_collector_exporters.keys() }}' 28 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platforms: 3 | - name: almalinux-9 4 | image: dokken/almalinux-9 5 | pre_build_image: true 6 | privileged: true 7 | cgroup_parent: docker.slice 8 | command: /lib/systemd/systemd 9 | - name: centos-stream-9 10 | image: dokken/centos-stream-9 11 | pre_build_image: true 12 | privileged: true 13 | cgroup_parent: docker.slice 14 | command: /lib/systemd/systemd 15 | - name: debian-11 16 | image: dokken/debian-11 17 | pre_build_image: true 18 | privileged: true 19 | cgroup_parent: docker.slice 20 | command: /lib/systemd/systemd 21 | - name: fedora-36 22 | image: dokken/fedora-36 23 | pre_build_image: true 24 | privileged: true 25 | cgroup_parent: docker.slice 26 | command: /lib/systemd/systemd 27 | - name: fedora-37 28 | image: dokken/fedora-37 29 | pre_build_image: true 30 | privileged: true 31 | cgroup_parent: docker.slice 32 | command: /lib/systemd/systemd 33 | - name: ubuntu-20.04 34 | image: dokken/ubuntu-20.04 35 | pre_build_image: true 36 | privileged: true 37 | cgroup_parent: docker.slice 38 | command: /lib/systemd/systemd 39 | - name: ubuntu-22.04 40 | image: dokken/ubuntu-22.04 41 | pre_build_image: true 42 | privileged: true 43 | cgroup_parent: docker.slice 44 | command: /lib/systemd/systemd 45 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/default/tests/test_default.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __metaclass__ = type 4 | 5 | import os 6 | import testinfra.utils.ansible_runner 7 | 8 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 9 | os.environ["MOLECULE_INVENTORY_FILE"] 10 | ).get_hosts("all") 11 | 12 | 13 | def test_directories(host): 14 | dirs = ["/etc/otel-collector", "/var/lib/otel-collector"] 15 | files = ["/etc/otel-collector/config.yaml"] 16 | for directory in dirs: 17 | d = host.file(directory) 18 | assert d.is_directory 19 | assert d.exists 20 | for file in files: 21 | f = host.file(file) 22 | assert f.exists 23 | assert f.is_file 24 | 25 | 26 | def test_service(host): 27 | s = host.service("otel-collector") 28 | # assert s.is_enabled 29 | assert s.is_running 30 | 31 | 32 | def test_socket(host): 33 | assert host.socket("tcp://127.0.0.1:9999").is_listening 34 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/latest/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Run role" 3 | hosts: all 4 | any_errors_fatal: true 5 | roles: 6 | - grafana.grafana.opentelemetry_collector 7 | vars: 8 | otel_collector_version: latest 9 | otel_collector_receivers: 10 | hostmetrics: 11 | scrapers: 12 | cpu: 13 | memory: 14 | otel_collector_processors: 15 | memory_limiter: 16 | check_interval: 2s 17 | limit_percentage: 80 18 | spike_limit_percentage: 25 19 | batch: 20 | otel_collector_exporters: 21 | prometheus: 22 | endpoint: '127.0.0.1:9999' 23 | otel_collector_service: 24 | pipelines: 25 | metrics: 26 | receivers: '{{ otel_collector_receivers.keys() }}' 27 | processors: '{{ otel_collector_processors.keys() }}' 28 | exporters: '{{ otel_collector_exporters.keys() }}' 29 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/latest/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platforms: 3 | - name: almalinux-9 4 | image: dokken/almalinux-9 5 | pre_build_image: true 6 | privileged: true 7 | cgroup_parent: docker.slice 8 | command: /lib/systemd/systemd 9 | - name: centos-stream-9 10 | image: dokken/centos-stream-9 11 | pre_build_image: true 12 | privileged: true 13 | cgroup_parent: docker.slice 14 | command: /lib/systemd/systemd 15 | - name: debian-11 16 | image: dokken/debian-11 17 | pre_build_image: true 18 | privileged: true 19 | cgroup_parent: docker.slice 20 | command: /lib/systemd/systemd 21 | - name: fedora-36 22 | image: dokken/fedora-36 23 | pre_build_image: true 24 | privileged: true 25 | cgroup_parent: docker.slice 26 | command: /lib/systemd/systemd 27 | - name: fedora-37 28 | image: dokken/fedora-37 29 | pre_build_image: true 30 | privileged: true 31 | cgroup_parent: docker.slice 32 | command: /lib/systemd/systemd 33 | - name: ubuntu-20.04 34 | image: dokken/ubuntu-20.04 35 | pre_build_image: true 36 | privileged: true 37 | cgroup_parent: docker.slice 38 | command: /lib/systemd/systemd 39 | - name: ubuntu-22.04 40 | image: dokken/ubuntu-22.04 41 | pre_build_image: true 42 | privileged: true 43 | cgroup_parent: docker.slice 44 | command: /lib/systemd/systemd 45 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/latest/tests/test_default.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __metaclass__ = type 4 | 5 | import os 6 | import testinfra.utils.ansible_runner 7 | 8 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 9 | os.environ["MOLECULE_INVENTORY_FILE"] 10 | ).get_hosts("all") 11 | 12 | 13 | def test_directories(host): 14 | dirs = [ 15 | "/etc/otel-collector", 16 | ] 17 | files = ["/etc/otel-collector/config.yaml"] 18 | for directory in dirs: 19 | d = host.file(directory) 20 | assert d.is_directory 21 | assert d.exists 22 | for file in files: 23 | f = host.file(file) 24 | assert f.exists 25 | assert f.is_file 26 | 27 | 28 | def test_service(host): 29 | s = host.service("otel-collector") 30 | # assert s.is_enabled 31 | assert s.is_running 32 | 33 | 34 | def test_socket(host): 35 | assert host.socket("tcp://127.0.0.1:9999").is_listening 36 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/non-contrib/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Run role" 3 | hosts: all 4 | any_errors_fatal: true 5 | roles: 6 | - grafana.grafana.opentelemetry_collector 7 | vars: 8 | otel_collector_type: '' 9 | otel_collector_receivers: 10 | hostmetrics: 11 | scrapers: 12 | cpu: 13 | memory: 14 | otel_collector_processors: 15 | memory_limiter: 16 | check_interval: 2s 17 | limit_percentage: 80 18 | spike_limit_percentage: 25 19 | batch: 20 | otel_collector_exporters: 21 | debug: 22 | otel_collector_service: 23 | pipelines: 24 | metrics: 25 | receivers: '{{ otel_collector_receivers.keys() }}' 26 | processors: '{{ otel_collector_processors.keys() }}' 27 | exporters: '{{ otel_collector_exporters.keys() }}' 28 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/non-contrib/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platforms: 3 | - name: almalinux-9 4 | image: dokken/almalinux-9 5 | pre_build_image: true 6 | privileged: true 7 | cgroup_parent: docker.slice 8 | command: /lib/systemd/systemd 9 | - name: centos-stream-9 10 | image: dokken/centos-stream-9 11 | pre_build_image: true 12 | privileged: true 13 | cgroup_parent: docker.slice 14 | command: /lib/systemd/systemd 15 | - name: debian-11 16 | image: dokken/debian-11 17 | pre_build_image: true 18 | privileged: true 19 | cgroup_parent: docker.slice 20 | command: /lib/systemd/systemd 21 | - name: fedora-36 22 | image: dokken/fedora-36 23 | pre_build_image: true 24 | privileged: true 25 | cgroup_parent: docker.slice 26 | command: /lib/systemd/systemd 27 | - name: fedora-37 28 | image: dokken/fedora-37 29 | pre_build_image: true 30 | privileged: true 31 | cgroup_parent: docker.slice 32 | command: /lib/systemd/systemd 33 | - name: ubuntu-20.04 34 | image: dokken/ubuntu-20.04 35 | pre_build_image: true 36 | privileged: true 37 | cgroup_parent: docker.slice 38 | command: /lib/systemd/systemd 39 | - name: ubuntu-22.04 40 | image: dokken/ubuntu-22.04 41 | pre_build_image: true 42 | privileged: true 43 | cgroup_parent: docker.slice 44 | command: /lib/systemd/systemd 45 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/molecule/non-contrib/tests/test_default.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __metaclass__ = type 4 | 5 | import os 6 | import testinfra.utils.ansible_runner 7 | 8 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 9 | os.environ["MOLECULE_INVENTORY_FILE"] 10 | ).get_hosts("all") 11 | 12 | 13 | def test_directories(host): 14 | dirs = [ 15 | "/etc/otel-collector", 16 | ] 17 | files = ["/etc/otel-collector/config.yaml"] 18 | for directory in dirs: 19 | d = host.file(directory) 20 | assert d.is_directory 21 | assert d.exists 22 | for file in files: 23 | f = host.file(file) 24 | assert f.exists 25 | assert f.is_file 26 | 27 | 28 | def test_service(host): 29 | s = host.service("otel-collector") 30 | # assert s.is_enabled 31 | assert s.is_running 32 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/tasks/configure.yml: -------------------------------------------------------------------------------- 1 | - name: Create OpenTelemetry Collector config directory 2 | ansible.builtin.file: 3 | path: "{{ otel_collector_config_dir }}" 4 | state: directory 5 | owner: "{{ otel_collector_service_user }}" 6 | group: "{{ otel_collector_service_group }}" 7 | mode: '0755' 8 | become: true 9 | 10 | - name: Deploy OpenTelemetry Collector configuration file 11 | ansible.builtin.template: 12 | src: otel_collector_config.yml.j2 13 | dest: "{{ otel_collector_config_dir }}/{{ otel_collector_config_file }}" 14 | owner: "{{ otel_collector_service_user }}" 15 | group: "{{ otel_collector_service_group }}" 16 | mode: '0644' 17 | validate: '{{ otel_collector_installation_dir }}/{{ otel_collector_executable }} --config=%s validate' 18 | notify: Restart OpenTelemetry Collector 19 | become: true 20 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/tasks/install.yml: -------------------------------------------------------------------------------- 1 | - name: Discover latest OpenTelemetry Collector version 2 | ansible.builtin.set_fact: 3 | otel_collector_version: "{{ (lookup('url', otel_collector_latest_url, split_lines=False) | from_json).get('tag_name') | replace('v', '') }}" 4 | run_once: true 5 | until: otel_collector_version is version('0.0.0', '>=') 6 | retries: 10 7 | when: 8 | - otel_collector_version == 'latest' 9 | 10 | - name: Discover installed version 11 | block: 12 | - name: Get installed version 13 | register: otel_collector_version_check 14 | changed_when: false 15 | failed_when: false 16 | check_mode: false # Always run, this does not change anything on the system 17 | ansible.builtin.command: '{{ otel_collector_installation_dir }}/{{ otel_collector_executable }} -v' 18 | 19 | - name: Set installed version variable 20 | ansible.builtin.set_fact: 21 | otel_collector_installed_version: "{{ otel_collector_version_check.stdout_lines[0] | default('otelcorecol version 0.0.0') | ansible.builtin.regex_search('\\d+\\.\\d+\\.\\d+') }}" 22 | 23 | - name: Create otel group 24 | ansible.builtin.group: 25 | name: "{{ otel_collector_service_group }}" 26 | system: true 27 | become: true 28 | 29 | - name: Create otel user 30 | ansible.builtin.user: 31 | name: "{{ otel_collector_service_user }}" 32 | group: "{{ otel_collector_service_group }}" 33 | system: true 34 | create_home: false # Appropriate for a system user, usually doesn't need a home directory 35 | become: true 36 | 37 | - name: Determine the architecture for OpenTelemetry Collector binary 38 | set_fact: 39 | otel_collector_arch: "{{ arch_mapping[ansible_facts['architecture']] | default('amd64') }}" 40 | vars: 41 | arch_mapping: 42 | x86_64: amd64 43 | aarch64: arm64 44 | armv7l: armhf 45 | i386: i386 46 | ppc64le: ppc64le 47 | 48 | - name: Download OpenTelemetry Collector binary 49 | ansible.builtin.get_url: 50 | url: "{{ otel_collector_binary_url }}" 51 | dest: "/tmp/otelcol-{{ otel_collector_type }}-{{ otel_collector_version }}.tar.gz" 52 | mode: '0755' 53 | become: true 54 | when: otel_collector_installed_version is version(otel_collector_version, '!=') 55 | 56 | - name: Remove existing OpenTelemetry Collector installation directory 57 | ansible.builtin.file: 58 | path: "{{ otel_collector_installation_dir }}" 59 | state: absent 60 | become: true 61 | when: otel_collector_installed_version is version(otel_collector_version, '!=') 62 | 63 | - name: Create OpenTelemetry Collector installation directory 64 | ansible.builtin.file: 65 | path: "{{ otel_collector_installation_dir }}" 66 | state: directory 67 | mode: '0755' 68 | owner: "{{ otel_collector_service_user }}" 69 | group: "{{ otel_collector_service_group }}" 70 | become: true 71 | 72 | - name: Extract OpenTelemetry Collector binary 73 | ansible.builtin.unarchive: 74 | src: "/tmp/otelcol-{{ otel_collector_type }}-{{ otel_collector_version }}.tar.gz" 75 | dest: "{{ otel_collector_installation_dir }}" 76 | remote_src: yes 77 | become: true 78 | register: extract_result 79 | when: not ansible_check_mode and otel_collector_installed_version is version(otel_collector_version, '!=') 80 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install OpenTelemetry Collector 2 | include_tasks: install.yml 3 | tags: [install] 4 | 5 | - name: Configure OpenTelemetry Collector 6 | include_tasks: configure.yml 7 | tags: [configure] 8 | 9 | - name: Manage OpenTelemetry Collector service 10 | include_tasks: service.yml 11 | tags: [service] 12 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/tasks/service.yml: -------------------------------------------------------------------------------- 1 | - name: Copy OpenTelemetry Collector systemd unit file 2 | ansible.builtin.template: 3 | src: otel_collector.service.j2 4 | dest: /etc/systemd/system/{{ otel_collector_service_name }}.service 5 | mode: '0644' 6 | become: true 7 | notify: Restart OpenTelemetry Collector 8 | 9 | - name: Reload systemd daemon to pick up changes 10 | ansible.builtin.systemd: 11 | daemon_reload: yes 12 | become: true 13 | 14 | - name: Ensure OpenTelemetry Collector service is enabled and running 15 | ansible.builtin.service: 16 | name: "{{ otel_collector_service_name }}" 17 | enabled: yes 18 | state: started 19 | become: true 20 | ignore_errors: '{{ ansible_check_mode | bool }}' 21 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/templates/otel_collector.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=OpenTelemetry Collector 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart={{ otel_collector_installation_dir }}/{{ otel_collector_executable }} --config={{ otel_collector_config_dir }}/{{ otel_collector_config_file }} 8 | User={{ otel_collector_service_user }} 9 | Group={{ otel_collector_service_group }} 10 | Restart=on-failure 11 | StateDirectory={{ otel_collector_service_statedirectory }} 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /roles/opentelemetry_collector/templates/otel_collector_config.yml.j2: -------------------------------------------------------------------------------- 1 | {% if otel_collector_receivers is defined and otel_collector_receivers | length > 0 %} 2 | receivers: 3 | {{ otel_collector_receivers | to_nice_yaml(indent=2) | indent(2, true) }} 4 | {% endif %} 5 | {% if otel_collector_processors is defined and otel_collector_processors | length > 0 %} 6 | processors: 7 | {{ otel_collector_processors | to_nice_yaml(indent=2) | indent(2, true) }} 8 | {% endif %} 9 | {% if otel_collector_exporters is defined and otel_collector_exporters | length > 0 %} 10 | exporters: 11 | {{ otel_collector_exporters | to_nice_yaml(indent=2) | indent(2, true) }} 12 | {% endif %} 13 | {% if otel_collector_extensions is defined and otel_collector_extensions | length > 0 %} 14 | extensions: 15 | {{ otel_collector_extensions | to_nice_yaml(indent=2) | indent(2, true) }} 16 | {% endif %} 17 | {% if otel_collector_service is defined and otel_collector_service | length > 0 %} 18 | service: 19 | {{ otel_collector_service | to_nice_yaml(indent=2) | indent(2, true) }} 20 | {% endif %} 21 | {% if otel_collector_connectors is defined and otel_collector_connectors | length > 0 %} 22 | connectors: 23 | {{ otel_collector_connectors | to_nice_yaml(indent=2) | indent(2, true) }} 24 | {% endif %} 25 | -------------------------------------------------------------------------------- /roles/promtail/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for promtail 3 | promtail_version: "latest" 4 | promtail_uninstall: false 5 | promtail_http_listen_port: 9080 6 | promtail_http_listen_address: "0.0.0.0" 7 | promtail_expose_port: false 8 | promtail_positions_path: "/var/lib/promtail" 9 | promtail_runtime_mode: "acl" # Supported "root" or "acl" 10 | promtail_extra_flags: [] 11 | # promtail_extra_flags: 12 | # - "-server.enable-runtime-reload" 13 | # - "-config.expand-env=true" 14 | 15 | promtail_user_append_groups: 16 | - "systemd-journal" 17 | 18 | promtail_download_url_rpm: "https://github.com/grafana/loki/releases/download/v{{ promtail_version }}/promtail-{{ promtail_version }}.{{ __promtail_arch }}.rpm" 19 | promtail_download_url_deb: "https://github.com/grafana/loki/releases/download/v{{ promtail_version }}/promtail_{{ promtail_version }}_{{ __promtail_arch }}.deb" 20 | 21 | # default variables for /etc/promtail/config.yml 22 | promtail_server: 23 | http_listen_port: "{{ promtail_http_listen_port }}" 24 | http_listen_address: "{{ promtail_http_listen_address }}" 25 | 26 | promtail_positions: 27 | filename: "{{ promtail_positions_path }}/positions.yaml" 28 | 29 | promtail_clients: [] 30 | # promtail_clients: 31 | # - url: http://localhost:3100/loki/api/v1/push 32 | 33 | promtail_scrape_configs: [] 34 | # promtail_scrape_configs: 35 | # - job_name: system 36 | # static_configs: 37 | # - targets: 38 | # - localhost 39 | # labels: 40 | # job: messages 41 | # instance: "{{ ansible_facts['fqdn'] }}" 42 | # __path__: /var/log/messages 43 | # - targets: 44 | # - localhost 45 | # labels: 46 | # job: nginx 47 | # instance: "{{ ansible_facts['fqdn'] }}" 48 | # __path__: /var/log/nginx/*.log 49 | 50 | # not set by default 51 | # promtail_limits_config: 52 | # promtail_target_config: 53 | -------------------------------------------------------------------------------- /roles/promtail/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for promtail 3 | - name: Restart promtail 4 | listen: "restart promtail" 5 | ansible.builtin.systemd: 6 | daemon_reload: true 7 | name: promtail.service 8 | state: restarted 9 | enabled: true 10 | when: not ansible_check_mode 11 | -------------------------------------------------------------------------------- /roles/promtail/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: promtail 4 | author: voidquark 5 | description: Manage Grafana Promtail Application 6 | license: "GPL-3.0-or-later" 7 | min_ansible_version: "2.10" 8 | platforms: 9 | - name: EL 10 | versions: 11 | - "9" 12 | - "8" 13 | - name: Fedora 14 | versions: 15 | - all 16 | - name: Debian 17 | versions: 18 | - all 19 | - name: Ubuntu 20 | versions: 21 | - all 22 | galaxy_tags: 23 | - promtail 24 | - grafana 25 | - logging 26 | - monitoring 27 | 28 | dependencies: [] 29 | -------------------------------------------------------------------------------- /roles/promtail/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | vars: 5 | promtail_scrape_configs: 6 | - job_name: test 7 | static_configs: 8 | - targets: 9 | - localhost 10 | labels: 11 | job: test 12 | instance: "{{ ansible_facts['fqdn'] }}" 13 | __path__: /var/log/last* 14 | roles: 15 | - role: grafana.grafana.promtail 16 | -------------------------------------------------------------------------------- /roles/promtail/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | ignore-errors: true 6 | driver: 7 | name: docker 8 | platforms: 9 | - name: instance 10 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest" 11 | command: ${MOLECULE_DOCKER_COMMAND:-""} 12 | volumes: 13 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 14 | cgroupns_mode: host 15 | privileged: true 16 | pre_build_image: true 17 | provisioner: 18 | name: ansible 19 | playbooks: 20 | converge: converge.yml 21 | -------------------------------------------------------------------------------- /roles/promtail/tasks/acl_configuration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for promtail deploy - acl configuration 3 | 4 | - name: Extract log files from static_configs - labels - path 5 | ansible.builtin.set_fact: 6 | __promtail_acl_log_files: >- 7 | {{ 8 | promtail_scrape_configs 9 | | selectattr('static_configs', 'defined') 10 | | map(attribute='static_configs') 11 | | flatten 12 | | map(attribute='labels') 13 | | map(attribute='__path__') 14 | | list 15 | }} 16 | 17 | - name: Extract log dirs paths from static_configs - labels - path 18 | ansible.builtin.set_fact: 19 | __promtail_acl_log_dirs: >- 20 | {{ 21 | promtail_scrape_configs 22 | | selectattr('static_configs', 'defined') 23 | | map(attribute='static_configs') 24 | | flatten 25 | | map(attribute='labels') 26 | | map(attribute='__path__') 27 | | map('dirname') 28 | | unique 29 | | list 30 | }} 31 | 32 | - name: Stat log dirs 33 | ansible.builtin.stat: 34 | path: "{{ item }}" 35 | loop: "{{ __promtail_acl_log_dirs }}" 36 | register: __promtail_stat_acl_log_dirs 37 | 38 | - name: Set recursive default ACL permission for log dirs 39 | ansible.posix.acl: 40 | path: "{{ item.item }}" 41 | recursive: true 42 | entity: promtail 43 | etype: user 44 | permissions: rx 45 | default: true 46 | state: present 47 | loop: "{{ __promtail_stat_acl_log_dirs.results }}" 48 | when: 49 | - __promtail_stat_acl_log_dirs | length > 0 50 | - item.stat.exists 51 | notify: restart promtail 52 | 53 | - name: Set ACL permission for log dirs 54 | ansible.posix.acl: 55 | path: "{{ item.item }}" 56 | entity: promtail 57 | etype: user 58 | permissions: rx 59 | state: present 60 | loop: "{{ __promtail_stat_acl_log_dirs.results }}" 61 | when: 62 | - __promtail_stat_acl_log_dirs | length > 0 63 | - item.stat.exists 64 | notify: restart promtail 65 | 66 | - name: Find all existing ACL log files 67 | ansible.builtin.find: 68 | paths: "{{ item | dirname }}" 69 | patterns: "{{ item | basename }}" 70 | loop: "{{ __promtail_acl_log_files }}" 71 | register: __promtail_find_files 72 | 73 | - name: Define existing ACL log files 74 | ansible.builtin.set_fact: 75 | __promtail_existing_acl_log_files: "{{ __promtail_find_files.results | map(attribute='files') | flatten | map(attribute='path') }}" 76 | 77 | - name: Set ACL permission for existing log files 78 | ansible.posix.acl: 79 | path: "{{ item }}" 80 | entity: promtail 81 | etype: user 82 | permissions: r 83 | state: present 84 | loop: "{{ __promtail_existing_acl_log_files }}" 85 | when: 86 | - __promtail_existing_acl_log_files | length > 0 87 | notify: restart promtail 88 | 89 | - name: Promtail ACL Logrotate 90 | when: 91 | - __promtail_acl_log_dirs | length > 0 92 | - __promtail_acl_log_files | length > 0 93 | block: 94 | - name: Template promtail ACL config for Logrotate 95 | ansible.builtin.template: 96 | src: "promtail_acl.j2" 97 | dest: "/etc/logrotate.d/promtail_acl" 98 | owner: "root" 99 | group: "root" 100 | mode: "0644" 101 | 102 | - name: Ensure that Promtail dummy dir for logrotate ACL configuration exist 103 | ansible.builtin.file: 104 | path: "/var/log/dummy_promtail_acl" 105 | state: directory 106 | owner: "promtail" 107 | group: "root" 108 | mode: "0750" 109 | 110 | - name: Create dummy empty log file for logrotate ACL configuration to work 111 | ansible.builtin.copy: 112 | content: "" 113 | dest: "/var/log/dummy_promtail_acl/dummy_promtail_acl.log" 114 | owner: "promtail" 115 | group: "root" 116 | mode: 0600 117 | -------------------------------------------------------------------------------- /roles/promtail/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for promtail 3 | - name: Include OS specific variables 4 | ansible.builtin.include_vars: 5 | file: "{{ ansible_facts['os_family'] }}.yml" 6 | 7 | - name: Deploy Promtail service 8 | ansible.builtin.include_tasks: 9 | file: "deploy.yml" 10 | when: not promtail_uninstall 11 | 12 | - name: Uninstall Promtail service 13 | ansible.builtin.include_tasks: 14 | file: "uninstall.yml" 15 | when: promtail_uninstall 16 | -------------------------------------------------------------------------------- /roles/promtail/tasks/setup-Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: APT - Ensure that ACL is present 3 | ansible.builtin.apt: 4 | name: "acl" 5 | state: present 6 | update_cache: yes 7 | when: promtail_runtime_mode == "acl" 8 | 9 | - name: APT - Install Promtail 10 | ansible.builtin.apt: 11 | deb: "{{ promtail_download_url_deb }}" 12 | state: present 13 | notify: restart promtail 14 | when: __current_deployed_version.stdout is not defined or promtail_version not in __current_deployed_version.stdout 15 | -------------------------------------------------------------------------------- /roles/promtail/tasks/setup-RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: DNF - Install Promtail from remote URL 3 | ansible.builtin.dnf: 4 | name: "{{ promtail_download_url_rpm }}" 5 | state: present 6 | disable_gpg_check: true 7 | notify: restart promtail 8 | when: 9 | - __current_deployed_version.stdout is not defined or promtail_version not in __current_deployed_version.stdout 10 | -------------------------------------------------------------------------------- /roles/promtail/tasks/uninstall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for promtail uninstall 3 | - name: Stop Promtail service 4 | ansible.builtin.systemd: # noqa ignore-errors 5 | name: promtail 6 | state: stopped 7 | ignore_errors: true 8 | 9 | - name: Extract log files from static_configs - labels - path 10 | ansible.builtin.set_fact: 11 | __promtail_acl_log_files: >- 12 | {{ 13 | promtail_scrape_configs 14 | | selectattr('static_configs', 'defined') 15 | | map(attribute='static_configs') 16 | | flatten 17 | | map(attribute='labels') 18 | | map(attribute='__path__') 19 | | list 20 | }} 21 | 22 | - name: Extract log dirs paths from static_configs - labels - path 23 | ansible.builtin.set_fact: 24 | __promtail_acl_log_dirs: >- 25 | {{ 26 | promtail_scrape_configs 27 | | selectattr('static_configs', 'defined') 28 | | map(attribute='static_configs') 29 | | flatten 30 | | map(attribute='labels') 31 | | map(attribute='__path__') 32 | | map('dirname') 33 | | list 34 | }} 35 | 36 | - name: Remove ACL Permission for log dirs - default 37 | ansible.posix.acl: # noqa ignore-errors 38 | path: "{{ item }}" 39 | recursive: true 40 | entity: promtail 41 | etype: user 42 | default: true 43 | state: absent 44 | loop: "{{ __promtail_acl_log_dirs }}" 45 | ignore_errors: true 46 | 47 | - name: Remove ACL permission for log dirs 48 | ansible.posix.acl: # noqa ignore-errors 49 | path: "{{ item }}" 50 | entity: promtail 51 | etype: user 52 | state: absent 53 | loop: "{{ __promtail_acl_log_dirs }}" 54 | ignore_errors: true 55 | 56 | - name: Find all existing ACL log files 57 | ansible.builtin.find: 58 | paths: "{{ item | dirname }}" 59 | patterns: "{{ item | basename }}" 60 | loop: "{{ __promtail_acl_log_files }}" 61 | register: __promtail_find_files 62 | 63 | - name: Define existing ACL log files 64 | ansible.builtin.set_fact: 65 | __promtail_existing_acl_log_files: "{{ __promtail_find_files.results | map(attribute='files') | flatten | map(attribute='path') }}" 66 | 67 | - name: Remove ACL Permission for existing log files 68 | ansible.posix.acl: # noqa ignore-errors 69 | path: "{{ item }}" 70 | entity: promtail 71 | etype: user 72 | state: absent 73 | loop: "{{ __promtail_existing_acl_log_files }}" 74 | ignore_errors: true 75 | 76 | - name: Uninstall Promtail rpm package 77 | ansible.builtin.dnf: 78 | name: "promtail" 79 | state: absent 80 | autoremove: true 81 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 82 | 83 | - name: Uninstall Promtail deb package 84 | ansible.builtin.apt: 85 | name: "promtail" 86 | state: absent 87 | purge: true 88 | when: ansible_facts['os_family'] == 'Debian' 89 | 90 | - name: Ensure that Promtail firewalld rule is not present - tcp port {{ promtail_http_listen_port }} 91 | ansible.posix.firewalld: # noqa ignore-errors 92 | immediate: true 93 | permanent: true 94 | port: "{{ promtail_http_listen_port }}/tcp" 95 | state: disabled 96 | ignore_errors: true 97 | 98 | - name: Remove Promtail directories" 99 | ansible.builtin.file: 100 | path: "{{ remove_me }}" 101 | state: absent 102 | loop: 103 | - "/etc/promtail" 104 | - "{{ promtail_positions_path }}" 105 | - "/etc/logrotate.d/promtail_acl" 106 | - "/var/log/dummy_promtail_acl" 107 | loop_control: 108 | loop_var: remove_me 109 | 110 | - name: Remove the Promtail system user 111 | ansible.builtin.user: 112 | name: "promtail" 113 | force: true 114 | state: absent 115 | 116 | - name: Remove Promtail system group 117 | ansible.builtin.group: 118 | name: "promtail" 119 | state: absent 120 | -------------------------------------------------------------------------------- /roles/promtail/templates/config.yml.j2: -------------------------------------------------------------------------------- 1 | server: 2 | {{ promtail_server | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 3 | {% if promtail_positions is defined %} 4 | positions: 5 | {{ promtail_positions | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 6 | {% endif %} 7 | {% if promtail_clients is defined %} 8 | clients: 9 | {{ promtail_clients | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 10 | {% endif %} 11 | {% if promtail_scrape_configs is defined %} 12 | scrape_configs: 13 | {{ promtail_scrape_configs | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 14 | {% endif %} 15 | {% if promtail_limits_config is defined %} 16 | limits_config: 17 | {{ promtail_limits_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 18 | {% endif %} 19 | {% if promtail_target_config is defined %} 20 | target_config: 21 | {{ promtail_target_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 22 | {% endif %} 23 | -------------------------------------------------------------------------------- /roles/promtail/templates/promtail.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Promtail service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | {% if promtail_runtime_mode == "acl" %} 8 | User=promtail 9 | {% elif promtail_runtime_mode == "root" %} 10 | User=root 11 | {% endif %} 12 | ExecStart=/usr/bin/promtail \ 13 | {% if promtail_extra_flags | length > 0 %} 14 | {% for flag in promtail_extra_flags %} 15 | {{ flag }} \ 16 | {% endfor %} 17 | {% endif %} 18 | -config.file /etc/promtail/config.yml 19 | 20 | TimeoutSec = 60 21 | Restart = on-failure 22 | RestartSec = 2 23 | 24 | [Install] 25 | -------------------------------------------------------------------------------- /roles/promtail/templates/promtail_acl.j2: -------------------------------------------------------------------------------- 1 | /var/log/dummy_promtail_acl/dummy_promtail_acl.log 2 | { 3 | copytruncate 4 | postrotate 5 | {% for each_dir in __promtail_acl_log_dirs %} 6 | /usr/bin/setfacl -R -m d:u:promtail:rx {{ each_dir }} 7 | /usr/bin/setfacl -m u:promtail:rx {{ each_dir }} 8 | {% endfor %} 9 | {% for each_log in __promtail_acl_log_files %} 10 | /usr/bin/setfacl -m u:promtail:r {{ each_log }} 11 | {% endfor %} 12 | endscript 13 | } 14 | -------------------------------------------------------------------------------- /roles/promtail/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __promtail_arch_map: 3 | x86_64: 'amd64' 4 | armv6l: 'arm' 5 | armv7l: 'arm' 6 | aarch64: 'arm64' 7 | 8 | __promtail_arch: "{{ __promtail_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}" 9 | -------------------------------------------------------------------------------- /roles/promtail/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __promtail_arch: "{{ ansible_facts['architecture'] }}" 3 | -------------------------------------------------------------------------------- /roles/tempo/README.md: -------------------------------------------------------------------------------- 1 | # Ansible role - Tempo 2 | 3 | [![License](https://img.shields.io/github/license/grafana/grafana-ansible-collection)](LICENSE) 4 | 5 | The Ansible Promtail Role allows you to effortlessly deploy and manage Tempo. 6 | This role is tailored for operating systems such as **RedHat**, **Rocky Linux**, **AlmaLinux**, **Ubuntu**, and **Debian**. 7 | 8 | 9 | ## Requirements 10 | 11 | - Ansible 2.10+ 12 | 13 | ## License 14 | 15 | See [LICENSE](https://github.com/grafana/grafana-ansible-collection/blob/main/LICENSE) 16 | -------------------------------------------------------------------------------- /roles/tempo/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for tempo 3 | tempo_version: "latest" 4 | tempo_uninstall: false 5 | __tempo_arch: "{{ arch_mapping[ansible_facts['architecture']] | default('amd64') }}" 6 | tempo_download_url_rpm: "https://github.com/grafana/tempo/releases/download/v{{ tempo_version }}/tempo_{{ tempo_version }}_linux_{{ __tempo_arch }}.rpm" 7 | tempo_download_url_deb: "https://github.com/grafana/tempo/releases/download/v{{ tempo_version }}/tempo_{{ tempo_version }}_linux_{{ __tempo_arch }}.deb" 8 | tempo_working_path: "/var/lib/tempo" 9 | tempo_http_listen_port: 3200 10 | tempo_http_listen_address: "0.0.0.0" 11 | tempo_log_level: warn 12 | tempo_report_usage: true 13 | tempo_multitenancy_enabled: false 14 | 15 | # Default Variables from /etc/tempo/config.yml 16 | tempo_server: 17 | http_listen_port: "{{ tempo_http_listen_port }}" 18 | http_listen_address: "{{ tempo_http_listen_address }}" 19 | log_level: "{{ tempo_log_level }}" 20 | 21 | tempo_query_frontend: 22 | search: 23 | duration_slo: 5s 24 | throughput_bytes_slo: 1.073741824e+09 25 | metadata_slo: 26 | duration_slo: 5s 27 | throughput_bytes_slo: 1.073741824e+09 28 | trace_by_id: 29 | duration_slo: 5s 30 | 31 | tempo_distributor: 32 | receivers: 33 | otlp: 34 | protocols: 35 | grpc: 36 | endpoint: "{{ tempo_http_listen_address }}:4317" 37 | 38 | tempo_metrics_generator: 39 | registry: 40 | external_labels: 41 | source: tempo 42 | cluster: docker-compose 43 | storage: 44 | path: "{{ tempo_working_path }}/generator/wal" 45 | remote_write: 46 | - url: http://prometheus:9090/api/v1/write 47 | send_exemplars: true 48 | traces_storage: 49 | path: "{{ tempo_working_path }}/generator/traces" 50 | 51 | tempo_storage: 52 | trace: 53 | backend: local # backend configuration to use 54 | wal: 55 | path: "{{ tempo_working_path }}/wal" # where to store the wal locally 56 | local: 57 | path: "{{ tempo_working_path }}/blocks" 58 | 59 | tempo_overrides: 60 | defaults: 61 | metrics_generator: 62 | processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator 63 | generate_native_histograms: both 64 | 65 | # Additional Config Variables for /etc/tempo/config.yml 66 | # tempo_ingester: 67 | # tempo_ingester_client: 68 | # tempo_metrics_generator_client: 69 | # tempo_querier: 70 | # tempo_compactor: 71 | # tempo_storage: 72 | # tempo_memberlist: 73 | # tempo_cache: 74 | -------------------------------------------------------------------------------- /roles/tempo/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Restart tempo 4 | ansible.builtin.systemd: 5 | name: tempo.service 6 | state: restarted 7 | -------------------------------------------------------------------------------- /roles/tempo/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: tempo 4 | author: Grafana 5 | description: Manage Grafana Tempo Application 6 | license: "GPL-3.0-or-later" 7 | min_ansible_version: "2.10" 8 | platforms: 9 | - name: EL 10 | versions: 11 | - "8" 12 | - "9" 13 | - name: Fedora 14 | versions: 15 | - all 16 | - name: Debian 17 | versions: 18 | - all 19 | - name: Ubuntu 20 | versions: 21 | - all 22 | galaxy_tags: 23 | - tempo 24 | - grafana 25 | - tracing 26 | - monitoring 27 | 28 | dependencies: [] 29 | -------------------------------------------------------------------------------- /roles/tempo/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | roles: 5 | - role: grafana.grafana.tempo 6 | -------------------------------------------------------------------------------- /roles/tempo/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | options: 5 | ignore-errors: true 6 | driver: 7 | name: docker 8 | platforms: 9 | - name: instance 10 | image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest" 11 | command: ${MOLECULE_DOCKER_COMMAND:-""} 12 | volumes: 13 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 14 | cgroupns_mode: host 15 | privileged: true 16 | pre_build_image: true 17 | provisioner: 18 | name: ansible 19 | playbooks: 20 | converge: converge.yml 21 | -------------------------------------------------------------------------------- /roles/tempo/tasks/deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for Tempo deployment 3 | - name: Obtain the latest version from the Tempo GitHub repo 4 | when: tempo_version == "latest" 5 | block: 6 | - name: Scrape Github API endpoint to obtain latest Tempo version 7 | ansible.builtin.uri: 8 | url: "https://api.github.com/repos/grafana/tempo/releases/latest" 9 | method: GET 10 | body_format: json 11 | become: false 12 | delegate_to: localhost 13 | run_once: true 14 | register: __github_latest_version 15 | 16 | - name: Latest available Tempo version 17 | ansible.builtin.set_fact: 18 | tempo_version: "{{ __github_latest_version.json.tag_name | regex_replace('^v?(\\d+\\.\\d+\\.\\d+)$', '\\1') }}" 19 | 20 | - name: Verify current deployed version 21 | block: 22 | - name: Check if Tempo binary is present 23 | ansible.builtin.stat: 24 | path: "/usr/bin/tempo" 25 | register: __already_deployed 26 | 27 | - name: Obtain current deployed Tempo version 28 | ansible.builtin.command: 29 | cmd: "/usr/bin/tempo --version" 30 | changed_when: false 31 | register: __current_deployed_version 32 | when: __already_deployed.stat.exists | bool 33 | 34 | - name: Include RedHat/Rocky setup 35 | ansible.builtin.include_tasks: 36 | file: setup-Redhat.yml 37 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 38 | 39 | - name: Include Debian/Ubuntu setup 40 | ansible.builtin.include_tasks: 41 | file: setup-Debian.yml 42 | when: ansible_facts['os_family'] == 'Debian' 43 | 44 | - name: Check if Tempo default dir is present 45 | ansible.builtin.stat: 46 | path: "/tmp/tempo/boltdb-shipper-active" 47 | register: __default_structure 48 | 49 | - name: Default structure cleanup 50 | when: __default_structure.stat.exists | bool 51 | block: 52 | - name: Ensure that Tempo is stopped before default cleanup 53 | ansible.builtin.systemd: 54 | name: tempo.service 55 | state: stopped 56 | 57 | - name: Remove default configuration from "/tmp/tempo" directory 58 | ansible.builtin.file: 59 | path: "/tmp/tempo" 60 | state: absent 61 | 62 | - name: Ensure that Tempo working path exists 63 | ansible.builtin.file: 64 | path: "{{ tempo_working_path }}" 65 | state: directory 66 | owner: "tempo" 67 | group: "root" 68 | mode: "0755" 69 | 70 | - name: Template Tempo config - /etc/tempo/config.yml 71 | ansible.builtin.template: 72 | src: "config.yml.j2" 73 | dest: "/etc/tempo/config.yml" 74 | owner: "tempo" 75 | group: "root" 76 | mode: "0644" 77 | validate: "/usr/bin/tempo -config.verify %s" 78 | notify: Restart tempo 79 | 80 | - name: Ensure restart has completed 81 | ansible.builtin.meta: flush_handlers 82 | 83 | - name: Ensure that Tempo is started 84 | ansible.builtin.systemd: 85 | name: tempo.service 86 | state: started 87 | enabled: true 88 | 89 | - name: Verify that Tempo URL is responding 90 | ansible.builtin.uri: 91 | url: "http://{{ tempo_http_listen_address }}:{{ tempo_http_listen_port }}/ready" 92 | method: GET 93 | register: tempo_verify_url_status_code 94 | retries: 5 95 | delay: 8 96 | until: tempo_verify_url_status_code.status == 200 97 | when: 98 | - not ansible_check_mode 99 | -------------------------------------------------------------------------------- /roles/tempo/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Deploy Tempo service 2 | ansible.builtin.include_tasks: 3 | file: "deploy.yml" 4 | when: not tempo_uninstall 5 | 6 | - name: Uninstall Tempo service 7 | ansible.builtin.include_tasks: 8 | file: "uninstall.yml" 9 | when: tempo_uninstall 10 | -------------------------------------------------------------------------------- /roles/tempo/tasks/setup-Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: APT - Install Tempo 3 | ansible.builtin.apt: 4 | deb: "{{ tempo_download_url_deb }}" 5 | state: present 6 | notify: Restart tempo 7 | when: __current_deployed_version.stdout is not defined or tempo_version not in __current_deployed_version.stdout 8 | -------------------------------------------------------------------------------- /roles/tempo/tasks/setup-Redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: DNF - Install Tempo from remote URL 3 | ansible.builtin.dnf: 4 | name: "{{ tempo_download_url_rpm }}" 5 | state: present 6 | disable_gpg_check: true 7 | notify: Restart tempo 8 | when: __current_deployed_version.stdout is not defined or tempo_version not in __current_deployed_version.stdout 9 | -------------------------------------------------------------------------------- /roles/tempo/tasks/uninstall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for tempo uninstall 3 | 4 | - name: Stop Tempo service 5 | ansible.builtin.systemd: # noqa ignore-errors 6 | name: tempo 7 | state: stopped 8 | ignore_errors: true 9 | 10 | - name: Uninstall Tempo rpm package 11 | ansible.builtin.dnf: 12 | name: "tempo" 13 | state: absent 14 | autoremove: true 15 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 16 | 17 | - name: Uninstall Tempo deb package 18 | ansible.builtin.apt: 19 | name: "tempo" 20 | state: absent 21 | purge: true 22 | when: ansible_facts['os_family'] == 'Debian' 23 | 24 | - name: Remove Tempo directories" 25 | ansible.builtin.file: 26 | path: "{{ remove_me }}" 27 | state: absent 28 | loop: 29 | - "/etc/tempo" 30 | - "{{ tempo_working_path }}" 31 | loop_control: 32 | loop_var: remove_me 33 | 34 | - name: Remove the Tempo system user 35 | ansible.builtin.user: 36 | name: "tempo" 37 | force: true 38 | state: absent 39 | 40 | - name: Remove Tempo system group 41 | ansible.builtin.group: 42 | name: "tempo" 43 | state: absent 44 | -------------------------------------------------------------------------------- /roles/tempo/templates/config.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | server: 3 | {{ tempo_server | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False) }} 4 | {% if tempo_distributor is defined %} 5 | distributor: 6 | {{ tempo_distributor | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 7 | {% endif %} 8 | {% if tempo_ingester is defined %} 9 | ingester: 10 | {{ tempo_ingester | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 11 | {% endif %} 12 | {% if tempo_ingester_client is defined %} 13 | ingester_client: 14 | {{ tempo_ingester_client | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 15 | {% endif %} 16 | {% if tempo_metrics_generator is defined %} 17 | metrics_generator: 18 | {{ tempo_metrics_generator | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 19 | {% endif %} 20 | {% if tempo_metrics_generator_client is defined %} 21 | metrics_generator_client: 22 | {{ tempo_metrics_generator_client | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 23 | {% endif %} 24 | {% if tempo_querier is defined %} 25 | querier: 26 | {{ tempo_querier | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 27 | {% endif %} 28 | {% if tempo_query_frontend is defined %} 29 | query_frontend: 30 | {{ tempo_query_frontend | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 31 | {% endif %} 32 | {% if tempo_compactor is defined %} 33 | compactor: 34 | {{ tempo_compactor | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 35 | {% endif %} 36 | {% if tempo_storage is defined %} 37 | storage: 38 | {{ tempo_storage | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 39 | {% endif %} 40 | {% if tempo_memberlist is defined %} 41 | memberlist: 42 | {{ tempo_memberlist | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 43 | {% endif %} 44 | {% if tempo_overrides is defined %} 45 | overrides: 46 | {{ tempo_overrides | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 47 | {% endif %} 48 | {% if tempo_cache is defined %} 49 | cache: 50 | {{ tempo_cache | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }} 51 | {% endif %} 52 | {% if tempo_http_api_prefix is defined %} 53 | http_api_prefix: {{ tempo_http_api_prefix }} 54 | {% endif %} 55 | usage_report: 56 | reporting_enabled: {{ tempo_report_usage }} 57 | multitenancy_enabled: {{ tempo_multitenancy_enabled }} 58 | -------------------------------------------------------------------------------- /roles/tempo/vars/main.yml: -------------------------------------------------------------------------------- 1 | __tempo_arch_map: 2 | x86_64: 'amd64' 3 | armv6l: 'arm' 4 | armv7l: 'arm' 5 | aarch64: 'arm64' 6 | __tempo_arch: "{{ __tempo_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}" 7 | -------------------------------------------------------------------------------- /tests/integration/requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /tests/integration/targets/alert_contact_point/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create Alerting contact point 3 | grafana.grafana.alert_contact_point: 4 | name: ops-email 5 | uid: opsemail 6 | type: email 7 | settings: 8 | addresses: ops@mydomain.com,devs@mydomain.com 9 | grafana_ini: 10 | server: 11 | root_url: "{{ grafana_url }}" 12 | grafana_api_key: "{{ grafana_api_key }}" 13 | state: present 14 | register: add_result 15 | 16 | - name: Add Check 17 | ansible.builtin.assert: 18 | that: 19 | - add_result.failed == false 20 | - add_result.output.provenance == "api" 21 | 22 | - name: Idempotency Check 23 | grafana.grafana.alert_contact_point: 24 | name: ops-email 25 | uid: opsemail 26 | type: email 27 | settings: 28 | addresses: ops@mydomain.com,devs@mydomain.com 29 | grafana_ini: 30 | server: 31 | root_url: "{{ grafana_url }}" 32 | grafana_api_key: "{{ grafana_api_key }}" 33 | state: present 34 | register: idempotent_result 35 | 36 | - name: Changed Check 37 | ansible.builtin.assert: 38 | that: 39 | - idempotent_result.changed == false 40 | - idempotent_result.output.provenance == "api" 41 | 42 | - name: Update Alerting contact point 43 | grafana.grafana.alert_contact_point: 44 | name: ops-email 45 | uid: opsemail 46 | type: email 47 | settings: 48 | addresses: "ops@mydomain.com,devs@mydomain.com,admin@mydomain.com" 49 | grafana_ini: 50 | server: 51 | root_url: "{{ grafana_url }}" 52 | grafana_api_key: "{{ grafana_api_key }}" 53 | state: present 54 | register: update_result 55 | 56 | - name: Failed Check 57 | ansible.builtin.assert: 58 | that: 59 | - update_result.failed == false 60 | - update_result.output.provenance == "api" 61 | 62 | - name: Delete Alerting contact point 63 | grafana.grafana.alert_contact_point: 64 | name: ops-email 65 | uid: opsemail 66 | type: email 67 | settings: 68 | addresses: "ops@mydomain.com,devs@mydomain.com,admin@mydomain.com" 69 | grafana_ini: 70 | server: 71 | root_url: "{{ grafana_url }}" 72 | grafana_api_key: "{{ grafana_api_key }}" 73 | state: absent 74 | register: delete_result 75 | 76 | - name: Delete Check 77 | ansible.builtin.assert: 78 | that: 79 | - delete_result.failed == false 80 | - delete_result.output.message == "contactpoint deleted" 81 | -------------------------------------------------------------------------------- /tests/integration/targets/alert_notification_policy/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Set Notification policy tree 2 | grafana.grafana.alert_notification_policy: 3 | grafana_ini: 4 | server: 5 | root_url: "{{ grafana_url }}" 6 | grafana_api_key: "{{ grafana_api_key }}" 7 | routes: [ 8 | { 9 | receiver: grafana-default-email, 10 | object_matchers: [["env", "=", "Production"]], 11 | } 12 | ] 13 | register: result 14 | 15 | - assert: 16 | that: 17 | - result.failed == false 18 | -------------------------------------------------------------------------------- /tests/integration/targets/cloud_api_key/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create Grafana Cloud API key 3 | grafana.grafana.cloud_api_key: 4 | name: ansible-integration-test 5 | role: Admin 6 | org_slug: "{{ org_name }}" 7 | existing_cloud_api_key: "{{ grafana_cloud_api_key }}" 8 | fail_if_already_created: false 9 | state: present 10 | register: add_result 11 | 12 | - name: Add Check 13 | ansible.builtin.assert: 14 | that: 15 | - add_result.output.name == "ansible-integration-test" 16 | when: add_result.output.name is defined 17 | 18 | - name: Re-run previous task 19 | grafana.grafana.cloud_api_key: 20 | name: ansible-integration-test 21 | role: Admin 22 | org_slug: "{{ org_name }}" 23 | existing_cloud_api_key: "{{ grafana_cloud_api_key }}" 24 | fail_if_already_created: false 25 | state: present 26 | register: update_result 27 | 28 | - name: Update Check 29 | ansible.builtin.assert: 30 | that: 31 | - update_result.output == "A Cloud API key with the same name already exists" 32 | 33 | - name: Delete Grafana Cloud API key 34 | grafana.grafana.cloud_api_key: 35 | name: ansible-integration-test 36 | role: Admin 37 | org_slug: "{{ org_name }}" 38 | existing_cloud_api_key: "{{ grafana_cloud_api_key }}" 39 | state: absent 40 | register: delete_result 41 | 42 | - name: Delete Check 43 | ansible.builtin.assert: 44 | that: 45 | - delete_result.output == "Cloud API key is deleted" 46 | -------------------------------------------------------------------------------- /tests/integration/targets/cloud_plugin/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add a plugin 3 | grafana.grafana.cloud_plugin: 4 | name: grafana-github-datasource 5 | version: 1.0.14 6 | stack_slug: "{{ test_stack_name }}" 7 | cloud_api_key: "{{ grafana_cloud_api_key }}" 8 | state: present 9 | register: add_result 10 | 11 | - name: Add Check 12 | ansible.builtin.assert: 13 | that: 14 | - add_result.pluginName == "GitHub" 15 | 16 | - name: Idempotency Check 17 | grafana.grafana.cloud_plugin: 18 | name: grafana-github-datasource 19 | version: 1.0.14 20 | stack_slug: "{{ test_stack_name }}" 21 | cloud_api_key: "{{ grafana_cloud_api_key }}" 22 | state: present 23 | register: idempotency_result 24 | 25 | - name: Idempotency Check 26 | ansible.builtin.assert: 27 | that: 28 | - idempotency_result.pluginName == "GitHub" 29 | 30 | - name: Update a plugin 31 | grafana.grafana.cloud_plugin: 32 | name: grafana-github-datasource 33 | version: 1.0.15 34 | stack_slug: "{{ test_stack_name }}" 35 | cloud_api_key: "{{ grafana_cloud_api_key }}" 36 | state: present 37 | register: update_result 38 | 39 | - name: Update Check 40 | ansible.builtin.assert: 41 | that: 42 | - update_result.pluginName == "GitHub" 43 | 44 | - name: Delete a plugin 45 | grafana.grafana.cloud_plugin: 46 | name: grafana-github-datasource 47 | version: 1.0.15 48 | stack_slug: "{{ test_stack_name }}" 49 | cloud_api_key: "{{ grafana_cloud_api_key }}" 50 | state: absent 51 | register: delete_result 52 | 53 | - name: Delete Check 54 | ansible.builtin.assert: 55 | that: 56 | - delete_result.pluginName == "GitHub" 57 | -------------------------------------------------------------------------------- /tests/integration/targets/create_cloud_stack/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create a Grafana Cloud stack 3 | grafana.grafana.cloud_stack: 4 | name: "{{ test_stack_name }}" 5 | stack_slug: "{{ test_stack_name }}" 6 | cloud_api_key: "{{ grafana_cloud_api_key }}" 7 | org_slug: "{{ org_name }}" 8 | state: present 9 | register: create_result 10 | 11 | - name: Create Check 12 | ansible.builtin.assert: 13 | that: 14 | - create_result.url == "https://" + "{{ test_stack_name }}" + ".grafana.net" 15 | 16 | - name: Sleep for 45 seconds 17 | ansible.builtin.wait_for: 18 | timeout: 45 19 | -------------------------------------------------------------------------------- /tests/integration/targets/dashboard/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create/Update a dashboard 3 | grafana.grafana.dashboard: 4 | dashboard: 5 | dashboard: 6 | uid: test1234 7 | title: Ansible Integration Test 8 | tags: 9 | - templated 10 | timezone: browser 11 | schemaVersion: 16 12 | refresh: 25s 13 | version: 0 14 | overwrite: true 15 | grafana_ini: 16 | server: 17 | root_url: "{{ grafana_url }}" 18 | grafana_api_key: "{{ grafana_api_key }}" 19 | state: present 20 | register: result_present 21 | 22 | - name: Create/Update Check 23 | ansible.builtin.assert: 24 | that: 25 | - result_present.changed == true 26 | - result_present.output.status == "success" 27 | 28 | - name: Delete dashboard 29 | grafana.grafana.dashboard: 30 | dashboard: 31 | dashboard: 32 | uid: test1234 33 | title: Ansible Integration Test 34 | tags: 35 | - templated 36 | timezone: browser 37 | schemaVersion: 16 38 | version: 0 39 | refresh: 25s 40 | overwrite: true 41 | grafana_ini: 42 | server: 43 | root_url: "{{ grafana_url }}" 44 | grafana_api_key: "{{ grafana_api_key }}" 45 | state: absent 46 | register: result_absent 47 | 48 | - name: Delete Check 49 | ansible.builtin.assert: 50 | that: 51 | - result_absent.changed == true 52 | - result_absent.output.message == "Dashboard Ansible Integration Test deleted" 53 | -------------------------------------------------------------------------------- /tests/integration/targets/datasource/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create/Update a Data Source 3 | grafana.grafana.datasource: 4 | dataSource: 5 | name: ansible-integration 6 | type: influxdb 7 | url: https://grafana.github.com/grafana-ansible-collection 8 | user: user 9 | secureJsonData: 10 | password: password 11 | database: db-name 12 | id: 123 13 | uid: ansibletest 14 | access: proxy 15 | grafana_ini: 16 | server: 17 | root_url: "{{ grafana_url }}" 18 | grafana_api_key: "{{ grafana_api_key }}" 19 | state: present 20 | register: create_result 21 | 22 | - name: Create Check 23 | ansible.builtin.assert: 24 | that: 25 | - create_result.changed == true 26 | - create_result.output.message == "Datasource added" or create_result.output.message == "Datasource updated" 27 | 28 | - name: Delete a Data Source 29 | grafana.grafana.datasource: 30 | dataSource: 31 | name: ansible-integration 32 | type: influxdb 33 | url: https://grafana.github.com/grafana-ansible-collection 34 | user: user 35 | secureJsonData: 36 | password: password 37 | database: db-name 38 | id: 123 39 | uid: ansibletest 40 | access: proxy 41 | grafana_ini: 42 | server: 43 | root_url: "{{ grafana_url }}" 44 | grafana_api_key: "{{ grafana_api_key }}" 45 | state: absent 46 | register: delete_result 47 | 48 | - name: Delete Check 49 | ansible.builtin.assert: 50 | that: 51 | - delete_result.changed == true 52 | - delete_result.output.response == "Data source deleted" 53 | -------------------------------------------------------------------------------- /tests/integration/targets/delete_cloud_stack/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Delete a Grafana Cloud stack 3 | grafana.grafana.cloud_stack: 4 | name: "{{ test_stack_name }}" 5 | stack_slug: "{{ test_stack_name }}" 6 | cloud_api_key: "{{ grafana_cloud_api_key }}" 7 | org_slug: "{{ org_name }}" 8 | state: absent 9 | register: delete_result 10 | 11 | - name: Delete Check 12 | ansible.builtin.assert: 13 | that: 14 | - delete_result.changed == true 15 | - delete_result.url == "https://" + "{{ test_stack_name }}" + ".grafana.net" 16 | 17 | - name: Sleep for 45 seconds 18 | ansible.builtin.wait_for: 19 | timeout: 45 20 | -------------------------------------------------------------------------------- /tests/integration/targets/folder/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create/Update a Folder in Grafana 3 | grafana.grafana.folder: 4 | title: Ansible Integration test 5 | uid: test123 6 | overwrite: true 7 | grafana_ini: 8 | server: 9 | root_url: "{{ grafana_url }}" 10 | grafana_api_key: "{{ grafana_api_key }}" 11 | state: present 12 | register: create_result 13 | 14 | - name: Create Check 15 | ansible.builtin.assert: 16 | that: 17 | - create_result.failed == false 18 | 19 | - name: Delete a folder 20 | grafana.grafana.folder: 21 | title: Ansible Integration test 22 | uid: test123 23 | overwrite: true 24 | grafana_ini: 25 | server: 26 | root_url: "{{ grafana_url }}" 27 | grafana_api_key: "{{ grafana_api_key }}" 28 | state: absent 29 | register: delete_result 30 | 31 | - name: Delete Check 32 | ansible.builtin.assert: 33 | that: 34 | - delete_result.output.status == 200 35 | - delete_result.output.response == "Folder has been succesfuly deleted" 36 | 37 | - name: Delete Idempotency Check 38 | grafana.grafana.folder: 39 | title: Ansible Integration test 40 | uid: test123 41 | overwrite: true 42 | grafana_ini: 43 | server: 44 | root_url: "{{ grafana_url }}" 45 | grafana_api_key: "{{ grafana_api_key }}" 46 | state: absent 47 | register: delete_result 48 | 49 | - name: Delete Check 50 | ansible.builtin.assert: 51 | that: 52 | - delete_result.output.status == 200 53 | - delete_result.output.response == "Folder does not exist" 54 | -------------------------------------------------------------------------------- /tests/integration/targets/molecule-grafana-alternative/runme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version="0.1.3" 4 | src="https://github.com/gardar/ansible-test-molecule/releases/download/$version/ansible-test-molecule.sh" 5 | 6 | # shellcheck disable=SC1090 7 | if [[ -v GITHUB_TOKEN ]] 8 | then 9 | source <(curl -L -s -H "Authorization: token $GITHUB_TOKEN" $src) 10 | else 11 | source <(curl -L -s $src) 12 | fi 13 | -------------------------------------------------------------------------------- /tests/integration/targets/molecule-grafana-default/runme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version="0.1.3" 4 | src="https://github.com/gardar/ansible-test-molecule/releases/download/$version/ansible-test-molecule.sh" 5 | 6 | # shellcheck disable=SC1090 7 | if [[ -v GITHUB_TOKEN ]] 8 | then 9 | source <(curl -L -s -H "Authorization: token $GITHUB_TOKEN" $src) 10 | else 11 | source <(curl -L -s $src) 12 | fi 13 | -------------------------------------------------------------------------------- /tests/integration/targets/user/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create an User in Grafana 3 | grafana.grafana.user: 4 | login: "grafana_user" 5 | password: "Yeihohghomi2neipuphuakooWeeph3ox" 6 | email: "grafana_user@localhost.local" 7 | name: "grafana user" 8 | grafana_url: "{{ grafana_url }}" 9 | admin_name: "admin" 10 | admin_password: "admin" 11 | state: present 12 | register: create_result 13 | 14 | - name: Create Check 15 | ansible.builtin.assert: 16 | that: 17 | - create_result.failed == false 18 | 19 | - name: Change an User password in Grafana 20 | grafana.grafana.user: 21 | login: "grafana_user" 22 | password: "Yeihohghomi2neipuphuakooWeeph3ox" 23 | grafana_url: "{{ grafana_url }}" 24 | admin_name: "admin" 25 | admin_password: "admin" 26 | state: update_password 27 | register: update_result 28 | 29 | - name: Create Check 30 | ansible.builtin.assert: 31 | that: 32 | - update_result.failed == false 33 | 34 | - name: Delete a user 35 | grafana.grafana.user: 36 | login: "grafana_user" 37 | grafana_url: "{{ grafana_url }}" 38 | admin_name: "admin" 39 | admin_password: "admin" 40 | state: absent 41 | register: delete_result 42 | 43 | - name: Delete Check 44 | ansible.builtin.assert: 45 | that: 46 | - delete_result.failed == false 47 | 48 | - name: Delete Idempotency Check 49 | grafana.grafana.user: 50 | login: "grafana_user" 51 | grafana_url: "{{ grafana_url }}" 52 | admin_name: "admin" 53 | admin_password: "admin" 54 | state: absent 55 | register: delete_result 56 | 57 | - name: Delete Check 58 | ansible.builtin.assert: 59 | that: 60 | - delete_result.failed == false 61 | -------------------------------------------------------------------------------- /tools/includes/logging.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | LOG_LEVEL=${LOG_LEVEL:=6} # 7 = debug -> 0 = emergency 4 | NO_COLOR="${NO_COLOR:-}" 5 | # shellcheck disable=SC2034 6 | TRACE="0" 7 | 8 | # _log 9 | # ----------------------------------- 10 | # Handles all logging, all log messages are output to stderr so stdout can still be piped 11 | # Example: _log "info" "Some message" 12 | # ----------------------------------- 13 | # shellcheck disable=SC2034 14 | _log () { 15 | local log_level="${1}" # first option is the level, the rest is the message 16 | shift 17 | local color_success="\\x1b[32m" 18 | local color_debug="\\x1b[36m" 19 | local color_info="\\x1b[90m" 20 | local color_notice="\\x1b[34m" 21 | local color_warning="\\x1b[33m" 22 | local color_error="\\x1b[31m" 23 | local color_critical="\\x1b[1;31m" 24 | local color_alert="\\x1b[1;33;41m" 25 | local color_emergency="\\x1b[1;4;5;33;41m" 26 | local colorvar="color_${log_level}" 27 | local color="${!colorvar:-${color_error}}" 28 | local color_reset="\\x1b[0m" 29 | 30 | # If no color is set or a non-recognized terminal is used don't use colors 31 | if [[ "${NO_COLOR:-}" = "true" ]] || { [[ "${TERM:-}" != "xterm"* ]] && [[ "${TERM:-}" != "screen"* ]]; } || [[ ! -t 2 ]]; then 32 | if [[ "${NO_COLOR:-}" != "false" ]]; then 33 | color=""; 34 | color_reset=""; 35 | fi 36 | fi 37 | 38 | # all remaining arguments are to be printed 39 | local log_line="" 40 | 41 | while IFS=$'\n' read -r log_line; do 42 | echo -e "$(date +"%Y-%m-%d %H:%M:%S %Z") ${color}[${log_level}]${color_reset} ${log_line}" 1>&2 43 | done <<< "${@:-}" 44 | } 45 | 46 | # emergency 47 | # ----------------------------------- 48 | # Handles emergency logging 49 | # ----------------------------------- 50 | emergency() { 51 | _log emergency "${@}"; exit 1; 52 | } 53 | 54 | # success 55 | # ----------------------------------- 56 | # Handles success logging 57 | # ----------------------------------- 58 | success() { 59 | _log success "${@}"; true; 60 | } 61 | 62 | # alert 63 | # ----------------------------------- 64 | # Handles alert logging 65 | # ----------------------------------- 66 | alert() { 67 | [[ "${LOG_LEVEL:-0}" -ge 1 ]] && _log alert "${@}"; 68 | true; 69 | } 70 | 71 | # critical 72 | # ----------------------------------- 73 | # Handles critical logging 74 | # ----------------------------------- 75 | critical() { 76 | [[ "${LOG_LEVEL:-0}" -ge 2 ]] && _log critical "${@}"; 77 | true; 78 | } 79 | 80 | # error 81 | # ----------------------------------- 82 | # Handles error logging 83 | # ----------------------------------- 84 | error() { 85 | [[ "${LOG_LEVEL:-0}" -ge 3 ]] && _log error "${@}"; 86 | true; 87 | } 88 | 89 | # warning 90 | # ----------------------------------- 91 | # Handles warning logging 92 | # ----------------------------------- 93 | warning() { 94 | [[ "${LOG_LEVEL:-0}" -ge 4 ]] && _log warning "${@}"; 95 | true; 96 | } 97 | 98 | # notice 99 | # ----------------------------------- 100 | # Handles notice logging 101 | # ----------------------------------- 102 | notice() { 103 | [[ "${LOG_LEVEL:-0}" -ge 5 ]] && _log notice "${@}"; 104 | true; 105 | } 106 | 107 | # info 108 | # ----------------------------------- 109 | # Handles info logging 110 | # ----------------------------------- 111 | info() { 112 | [[ "${LOG_LEVEL:-0}" -ge 6 ]] && _log info "${@}"; 113 | true; 114 | } 115 | 116 | # debug 117 | # ----------------------------------- 118 | # Handles debug logging and prepends the name of the that called debug in front of the message 119 | # ----------------------------------- 120 | debug() { 121 | [[ "${LOG_LEVEL:-0}" -ge 7 ]] && _log debug "${FUNCNAME[1]}() ${*}"; 122 | true; 123 | } 124 | -------------------------------------------------------------------------------- /tools/includes/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # heading 4 | # ----------------------------------- 5 | # Print standard heading 6 | # ----------------------------------- 7 | heading() { 8 | local title="${1}" 9 | local message="${2}" 10 | local width="75" 11 | local orange="\\033[38;5;202m" 12 | local reset="\\033[0m" 13 | local bold="\\x1b[1m" 14 | echo "" 15 | echo -e "${orange} ▒▒▓▓▒▒▒▓ ${reset} ____ __ _ _ " 16 | echo -e "${orange} ▓▓▓ ▒ ${reset} / ___| _ __ __ _ / _| __ _ _ __ __ _ | | __ _ | |__ ___ " 17 | echo -e "${orange} ▒▓ ▒▒▒▒▓ ${reset} | | _ | '__| / _ || |_ / _ || '_ \\ / _ | | | / _ || '_ \\ / __|" 18 | echo -e "${orange} ▒▓▓ ▒ ▒▒ ${reset} | |_| || | | (_| || _|| (_| || | | || (_| | | |___ | (_| || |_) | \\__\\" 19 | echo -e "${orange} ▒▓▒ ▒▓ ${reset} \\____||_| \\__,_||_| \\__,_||_| |_| \\__,_| |_____| \\__,_||_.__/ |___/" 20 | echo -e "${orange} ▒▒▒ ▒▒▒ ${reset} " 21 | echo -e "${orange} ▒▒▒▒▒ ${reset} $(repeat $(( ((width - ${#title}) - 2) / 2)) " ")${bold}$title${reset}" 22 | echo -e "${reset} $(repeat $(( ((width - ${#message}) - 2) / 2)) " ")$message${reset}" 23 | echo "" 24 | } 25 | 26 | # repeat 27 | # ----------------------------------- 28 | # Repeat a Character N number of times 29 | # ----------------------------------- 30 | repeat(){ 31 | local times="${1:-80}" 32 | local character="${2:-=}" 33 | local start=1 34 | local range 35 | range=$(seq "$start" "$times") 36 | local str="" 37 | # shellcheck disable=SC2034 38 | for i in $range; do 39 | str="$str${character}" 40 | done 41 | echo "$str" 42 | } 43 | 44 | # lintWarning 45 | # ----------------------------------- 46 | # Output a Lint Warning Message 47 | # ----------------------------------- 48 | lintWarning() { 49 | local msg="${1}" 50 | local color_warning="\\x1b[33m" 51 | local color_reset="\\x1b[0m" 52 | local bold="\\x1b[1m" 53 | echo -e " ‣ ${color_warning}${bold}[warn]${color_reset} $msg" 54 | } 55 | 56 | # lintError 57 | # ----------------------------------- 58 | # Output a Lint Error Message 59 | # ----------------------------------- 60 | lintError() { 61 | local msg="${1}" 62 | local color_error="\\x1b[31m" 63 | local color_reset="\\x1b[0m" 64 | local bold="\\x1b[1m" 65 | echo -e " ‣ ${color_error}${bold}[error]${color_reset} $msg" 66 | } 67 | -------------------------------------------------------------------------------- /tools/lint-ansible.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/tools/includes/utils.sh" 4 | 5 | source "./tools/includes/logging.sh" 6 | 7 | # output the heading 8 | heading "Grafana Ansible Collection" "Performing Ansible Linting using ansible-lint" 9 | 10 | # make sure pipenv exists 11 | if [[ "$(command -v pipenv)" = "" ]]; then 12 | echo >&2 "pipenv command is required, see (https://pipenv.pypa.io/en/latest/) or run: brew install pipenv"; 13 | exit 1; 14 | fi 15 | 16 | # make sure yamllint exists 17 | if [[ "$(pipenv run pip freeze | grep -c "ansible-lint")" == "0" ]]; then 18 | echo >&2 "ansible-lint command is required, see (https://pypi.org/project/ansible-lint/). Run \"make install\" to install it."; 19 | exit 1; 20 | fi 21 | 22 | # determine whether or not the script is called directly or sourced 23 | (return 0 2>/dev/null) && sourced=1 || sourced=0 24 | 25 | # run yamllint 26 | echo "$(pwd)/.ansible-lint" 27 | pipenv run ansible-lint --config-file "$(pwd)/.ansible-lint" --strict 28 | statusCode="$?" 29 | 30 | if [[ "$statusCode" == "0" ]]; then 31 | echo "no issues found" 32 | echo "" 33 | fi 34 | 35 | echo "" 36 | # if the script was called by another, send a valid exit code 37 | if [[ "$sourced" == "1" ]]; then 38 | return "$statusCode" 39 | fi 40 | -------------------------------------------------------------------------------- /tools/lint-editorconfig.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/tools/includes/utils.sh" 4 | 5 | source "./tools/includes/logging.sh" 6 | 7 | # output the heading 8 | heading "Grafana Ansible Collection" "Performing Editorconfig Linting using editorconfig-checker" 9 | 10 | # check to see if remark is installed 11 | if [[ ! -f "$(pwd)"/node_modules/.bin/editorconfig-checker ]]; then 12 | emergency "editorconfig-checker node module is not installed, please run: make install"; 13 | fi 14 | 15 | # determine whether or not the script is called directly or sourced 16 | (return 0 2>/dev/null) && sourced=1 || sourced=0 17 | 18 | statusCode=0 19 | ./node_modules/.bin/editorconfig-checker -config="$(pwd)/.editorconfig" -exclude "LICENSE|.+\.txt|.+\.py" 20 | currentCode="$?" 21 | # only override the statusCode if it is 0 22 | if [[ "$statusCode" == 0 ]]; then 23 | statusCode="$currentCode" 24 | fi 25 | 26 | if [[ "$statusCode" == "0" ]]; then 27 | echo "no issues found" 28 | echo "" 29 | fi 30 | 31 | echo "" 32 | 33 | # if the script was called by another, send a valid exit code 34 | if [[ "$sourced" == "1" ]]; then 35 | return "$statusCode" 36 | else 37 | exit "$statusCode" 38 | fi 39 | -------------------------------------------------------------------------------- /tools/lint-markdown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/tools/includes/utils.sh" 4 | 5 | source "./tools/includes/logging.sh" 6 | 7 | # output the heading 8 | heading "Grafana Ansible Collection" "Performing Markdown Linting using markdownlint" 9 | 10 | # check to see if remark is installed 11 | if [[ ! -f "$(pwd)"/node_modules/.bin/markdownlint-cli2 ]]; then 12 | emergency "markdownlint-cli2 node module is not installed, please run: make install"; 13 | fi 14 | 15 | # determine whether or not the script is called directly or sourced 16 | (return 0 2>/dev/null) && sourced=1 || sourced=0 17 | 18 | statusCode=0 19 | while read -r dir; do 20 | info "Checking file/directory: $dir" 21 | ./node_modules/.bin/markdownlint-cli2-config "$(pwd)/.markdownlint.yaml" "$dir" 22 | currentCode="$?" 23 | # if the current code is 0, output the file name for logging purposes 24 | if [[ "$currentCode" == 0 ]]; then 25 | echo -e "\\x1b[32m$dir\\x1b[0m: no issues found" 26 | fi 27 | # only override the statusCode if it is 0 28 | if [[ "$statusCode" == 0 ]]; then 29 | statusCode="$currentCode" 30 | fi 31 | echo "" 32 | done < <(find . -type f -name "*.md" -not -path "./node_modules/*" -not -path "./.git/*" -print0 | \ 33 | xargs -0 dirname | \ 34 | sort -nr | \ 35 | uniq | \ 36 | sort | \ 37 | xargs printf -- '%s/*.md\n' | \ 38 | sed 's|\./\*\.md|./README.md|' 39 | ) 40 | 41 | echo "" 42 | echo "" 43 | 44 | # if the script was called by another, send a valid exit code 45 | if [[ "$sourced" == "1" ]]; then 46 | return "$statusCode" 47 | else 48 | exit "$statusCode" 49 | fi 50 | -------------------------------------------------------------------------------- /tools/lint-shell.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/tools/includes/utils.sh" 4 | 5 | source "./tools/includes/logging.sh" 6 | 7 | # output the heading 8 | heading "Grafana Ansible Collection" "Performing Shell Linting using shellcheck" 9 | 10 | # check to see if shellcheck is installed 11 | if [[ "$(command -v shellcheck)" = "" ]]; then 12 | emergency "shellcheck is required if running lint locally, see: (https://shellcheck.net) or run: brew install nvm && nvm install 18"; 13 | fi 14 | 15 | # determine whether or not the script is called directly or sourced 16 | (return 0 2>/dev/null) && sourced=1 || sourced=0 17 | 18 | statusCode=0 19 | while read -r file; do 20 | shellcheck \ 21 | --external-sources \ 22 | --shell bash \ 23 | --source-path "$(dirname "$file")" \ 24 | "$file" 25 | currentCode="$?" 26 | # if the current code is 0, output the file name for logging purposes 27 | if [[ "$currentCode" == 0 ]]; then 28 | echo -e "\\x1b[32m$file\\x1b[0m: no issues found" 29 | else 30 | echo "" 31 | fi 32 | # only override the statusCode if it is 0 33 | if [[ "$statusCode" == 0 ]]; then 34 | statusCode="$currentCode" 35 | fi 36 | done < <(find . -type f -name "*.sh" -not -path "./node_modules/*" -not -path "./.git/*") 37 | 38 | echo "" 39 | echo "" 40 | 41 | # if the script was called by another, send a valid exit code 42 | if [[ "$sourced" == "1" ]]; then 43 | return "$statusCode" 44 | else 45 | exit "$statusCode" 46 | fi 47 | -------------------------------------------------------------------------------- /tools/lint-text.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/tools/includes/utils.sh" 4 | 5 | source "./tools/includes/logging.sh" 6 | 7 | # output the heading 8 | heading "Grafana Ansible Collections" "Performing Text Linting using textlint" 9 | 10 | # check to see if remark is installed 11 | if [[ ! -f "$(pwd)"/node_modules/.bin/textlint ]]; then 12 | emergency "remark node module is not installed, please run: make install"; 13 | fi 14 | 15 | # determine whether or not the script is called directly or sourced 16 | (return 0 2>/dev/null) && sourced=1 || sourced=0 17 | 18 | statusCode=0 19 | while read -r file; do 20 | "$(pwd)"/node_modules/.bin/textlint --config "$(pwd)/.textlintrc" "$file" 21 | currentCode="$?" 22 | # if the current code is 0, output the file name for logging purposes 23 | if [[ "$currentCode" == 0 ]]; then 24 | echo -e "\\x1b[32m$file\\x1b[0m: no issues found" 25 | fi 26 | # only override the statusCode if it is 0 27 | if [[ "$statusCode" == 0 ]]; then 28 | statusCode="$currentCode" 29 | fi 30 | done < <(find . -type f -name "*.md" -not -path "./node_modules/*" -not -path "./.git/*") 31 | 32 | echo "" 33 | echo "" 34 | 35 | # if the script was called by another, send a valid exit code 36 | if [[ "$sourced" == "1" ]]; then 37 | return "$statusCode" 38 | else 39 | exit "$statusCode" 40 | fi 41 | -------------------------------------------------------------------------------- /tools/lint-yaml.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/tools/includes/utils.sh" 4 | 5 | source "./tools/includes/logging.sh" 6 | 7 | # output the heading 8 | heading "Grafana Ansible Collection" "Performing YAML Linting using yamllint" 9 | 10 | # make sure pipenv exists 11 | if [[ "$(command -v pipenv)" = "" ]]; then 12 | echo >&2 "pipenv command is required, see (https://pipenv.pypa.io/en/latest/) or run: brew install pipenv"; 13 | exit 1; 14 | fi 15 | 16 | # make sure yamllint exists 17 | if [[ "$(pipenv run pip freeze | grep -c "yamllint")" == "0" ]]; then 18 | echo >&2 "yamllint command is required, see (https://pypi.org/project/yamllint/). Run \"make install\" to install it."; 19 | exit 1; 20 | fi 21 | 22 | # determine whether or not the script is called directly or sourced 23 | (return 0 2>/dev/null) && sourced=1 || sourced=0 24 | 25 | # run yamllint 26 | pipenv run yamllint --strict --config-file "$(pwd)/.yamllint" . 27 | statusCode="$?" 28 | 29 | if [[ "$statusCode" == "0" ]]; then 30 | echo "no issues found" 31 | echo "" 32 | fi 33 | 34 | # if the script was called by another, send a valid exit code 35 | if [[ "$sourced" == "1" ]]; then 36 | return "$statusCode" 37 | fi 38 | -------------------------------------------------------------------------------- /tools/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(pwd)/tools/includes/utils.sh" 4 | 5 | source "./tools/includes/logging.sh" 6 | 7 | # output the heading 8 | heading "Grafana Ansible Collection" "Performing Setup Checks" 9 | 10 | # make sure Node exists 11 | info "Checking to see if Node is installed" 12 | if [[ "$(command -v node)" = "" ]]; then 13 | warning "node is required if running lint locally, see: (https://nodejs.org) or run: brew install nvm && nvm install 18"; 14 | else 15 | success "node is installed" 16 | fi 17 | 18 | # make sure yarn exists 19 | info "Checking to see if yarn is installed" 20 | if [[ "$(command -v yarn)" = "" ]]; then 21 | warning "yarn is required if running lint locally, see: (https://yarnpkg.com) or run: brew install yarn"; 22 | else 23 | success "yarn is installed" 24 | fi 25 | 26 | # make sure shellcheck exists 27 | info "Checking to see if shellcheck is installed" 28 | if [[ "$(command -v shellcheck)" = "" ]]; then 29 | warning "shellcheck is required if running lint locally, see: (https://shellcheck.net) or run: brew install nvm && nvm install 18"; 30 | else 31 | success "shellcheck is installed" 32 | fi 33 | 34 | # make sure pipenv exists 35 | if [[ "$(command -v pipenv)" = "" ]]; then 36 | warning "pipenv command is required, see (https://pipenv.pypa.io/en/latest/) or run: brew install pipenv"; 37 | else 38 | success "pipenv is installed" 39 | fi 40 | --------------------------------------------------------------------------------