├── roles ├── mimir │ ├── files │ │ └── .gitkeep │ ├── vars │ │ └── .gitkeep │ ├── templates │ │ ├── .gitkeep │ │ └── config.yml.j2 │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── main.yml │ │ ├── setup-Debian.yml │ │ ├── setup-Redhat.yml │ │ ├── uninstall.yml │ │ └── deploy.yml │ ├── meta │ │ └── main.yml │ ├── molecule │ │ └── default │ │ │ ├── tests │ │ │ └── test_default.py │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ └── defaults │ │ └── main.yml ├── grafana │ ├── molecule │ │ ├── default │ │ │ ├── molecule.yml │ │ │ ├── converge.yml │ │ │ └── tests │ │ │ │ └── test_default.py │ │ └── alternative │ │ │ ├── molecule.yml │ │ │ ├── tests │ │ │ └── test_alternative.py │ │ │ └── converge.yml │ ├── test-requirements.txt │ ├── templates │ │ ├── tmpfiles.j2 │ │ ├── grafana.ini.j2 │ │ └── ldap.toml.j2 │ ├── vars │ │ └── distro │ │ │ ├── redhat.yml │ │ │ ├── debian.yml │ │ │ └── suse.yml │ ├── meta │ │ └── main.yml │ ├── tasks │ │ ├── plugins.yml │ │ ├── notifications.yml │ │ ├── api_keys.yml │ │ └── datasources.yml │ └── handlers │ │ └── main.yml ├── alloy │ ├── vars │ │ ├── Debian.yml │ │ ├── Suse.yml │ │ ├── RedHat.yml │ │ ├── Darwin.yml │ │ └── main.yml │ ├── templates │ │ ├── config.alloy.j2 │ │ ├── override.conf.j2 │ │ └── alloy.j2 │ ├── tasks │ │ ├── setup-Debian.yml │ │ ├── setup-RedHat.yml │ │ ├── setup-Suse.yml │ │ ├── main.yml │ │ ├── preflight.yml │ │ ├── uninstall.yml │ │ └── setup-Darwin.yml │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ └── defaults │ │ └── main.yml ├── loki │ ├── vars │ │ ├── RedHat.yml │ │ └── Debian.yml │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── templates │ │ └── rules.yml.j2 │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── setup-Debian.yml │ │ ├── setup-RedHat.yml │ │ ├── main.yml │ │ └── uninstall.yml │ ├── meta │ │ └── main.yml │ └── defaults │ │ └── main.yml ├── promtail │ ├── vars │ │ ├── RedHat.yml │ │ └── Debian.yml │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── setup-RedHat.yml │ │ ├── main.yml │ │ ├── setup-Debian.yml │ │ ├── uninstall.yml │ │ └── acl_configuration.yml │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── templates │ │ ├── promtail_acl.j2 │ │ ├── promtail.service.j2 │ │ └── config.yml.j2 │ ├── meta │ │ └── main.yml │ └── defaults │ │ └── main.yml ├── tempo │ ├── molecule │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── handlers │ │ └── main.yml │ ├── vars │ │ └── main.yml │ ├── tasks │ │ ├── main.yml │ │ ├── setup-Debian.yml │ │ ├── setup-Redhat.yml │ │ ├── uninstall.yml │ │ └── deploy.yml │ ├── README.md │ ├── meta │ │ └── main.yml │ ├── templates │ │ └── config.yml.j2 │ └── defaults │ │ └── main.yml ├── opentelemetry_collector │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── main.yml │ │ ├── service.yml │ │ ├── configure.yml │ │ └── install.yml │ ├── templates │ │ ├── otel_collector.service.j2 │ │ └── otel_collector_config.yml.j2 │ ├── meta │ │ └── main.yml │ ├── molecule │ │ ├── non-contrib │ │ │ ├── converge.yml │ │ │ ├── tests │ │ │ │ └── test_default.py │ │ │ └── molecule.yml │ │ ├── default │ │ │ ├── converge.yml │ │ │ ├── tests │ │ │ │ └── test_default.py │ │ │ └── molecule.yml │ │ ├── default-check-first │ │ │ ├── converge.yml │ │ │ ├── tests │ │ │ │ └── test_default.py │ │ │ └── molecule.yml │ │ └── latest │ │ │ ├── converge.yml │ │ │ ├── tests │ │ │ └── test_default.py │ │ │ └── molecule.yml │ └── defaults │ │ └── main.yml └── grafana_agent │ ├── templates │ ├── EnvironmentFile.j2 │ ├── grafana-agent.service.j2 │ └── config.yaml.j2 │ ├── handlers │ └── main.yaml │ ├── tasks │ ├── install │ │ ├── local-install.yaml │ │ ├── directories.yaml │ │ ├── user-group.yaml │ │ └── download-install.yaml │ ├── preflight.yaml │ ├── install.yaml │ ├── ga-started.yaml │ ├── preflight │ │ ├── systemd.yaml │ │ ├── download.yaml │ │ ├── vars.yaml │ │ └── install.yaml │ ├── main.yaml │ └── configure.yaml │ ├── meta │ └── main.yaml │ └── vars │ └── main.yaml ├── tests └── integration │ ├── requirements.txt │ └── targets │ ├── molecule-grafana-default │ └── runme.sh │ ├── molecule-grafana-alternative │ └── runme.sh │ ├── alert_notification_policy │ └── tasks │ │ └── main.yml │ ├── create_cloud_stack │ └── tasks │ │ └── main.yml │ ├── delete_cloud_stack │ └── tasks │ │ └── main.yml │ ├── cloud_api_key │ └── tasks │ │ └── main.yml │ ├── dashboard │ └── tasks │ │ └── main.yml │ ├── folder │ └── tasks │ │ └── main.yml │ ├── datasource │ └── tasks │ │ └── main.yml │ ├── cloud_plugin │ └── tasks │ │ └── main.yml │ ├── user │ └── tasks │ │ └── main.yml │ └── alert_contact_point │ └── tasks │ └── main.yml ├── ansible.cfg ├── meta └── runtime.yml ├── requirements.txt ├── examples ├── loki-basic-no-options.yml ├── agent-basic-no-options.yaml ├── agent-mode-flow-with-dynamic-conf.yaml ├── alloy.yaml ├── promtail-multiple-logs.yml ├── mimir-3-hosts.yaml ├── mimir-single-host.yaml ├── opentelemetry-collector.yml ├── ansible.cfg ├── loki-local-filesystem-with-retention-and-alert.yml └── agent-send-to-grafana-cloud.yaml ├── .yamllint ├── .shellcheckrc ├── Pipfile ├── requirements.yml ├── CODEOWNERS ├── .ansible-lint ├── .github ├── dependabot.yml └── workflows │ ├── loki-molecule.yml │ ├── promtail-molecule.yml │ ├── alloy-molecule.yml │ ├── opentelemetry-collector-molecule.yml │ ├── mimir-molecule.yml │ ├── lint.yaml │ ├── full-integration-test.yml │ ├── roles-test.yml │ └── modules-test.yml ├── galaxy.yml ├── catalog-info.yaml ├── package.json ├── .editorconfig ├── changelogs ├── config.yaml └── .plugin-cache.yaml ├── tools ├── lint-editorconfig.sh ├── lint-yaml.sh ├── lint-ansible.sh ├── lint-text.sh ├── setup.sh ├── lint-shell.sh ├── lint-markdown.sh └── includes │ ├── utils.sh │ └── logging.sh ├── .config └── molecule │ └── config.yml └── Makefile /roles/mimir/files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /roles/mimir/vars/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /roles/mimir/templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | collections_paths = ./ 3 | -------------------------------------------------------------------------------- /roles/grafana/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /roles/grafana/molecule/alternative/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | --- 2 | requires_ansible: ">=2.12.0,<3.0.0" 3 | -------------------------------------------------------------------------------- /roles/alloy/vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __alloy_env_file: "/etc/default/alloy" 3 | -------------------------------------------------------------------------------- /roles/alloy/vars/Suse.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __alloy_env_file: "/etc/sysconfig/alloy" 3 | -------------------------------------------------------------------------------- /roles/alloy/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __alloy_env_file: "/etc/sysconfig/alloy" 3 | -------------------------------------------------------------------------------- /roles/alloy/templates/config.alloy.j2: -------------------------------------------------------------------------------- 1 | // Ansible Managed 2 | 3 | {{ alloy_config }} 4 | -------------------------------------------------------------------------------- /roles/loki/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __loki_arch: "{{ ansible_facts['architecture'] }}" 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /roles/alloy/templates/override.conf.j2: -------------------------------------------------------------------------------- 1 | # Ansible Managed 2 | 3 | {{ alloy_systemd_override }} 4 | -------------------------------------------------------------------------------- /roles/promtail/vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | __promtail_arch: "{{ ansible_facts['architecture'] }}" 3 | -------------------------------------------------------------------------------- /roles/grafana/test-requirements.txt: -------------------------------------------------------------------------------- 1 | molecule 2 | docker 3 | pytest-testinfra 4 | jmespath 5 | selinux 6 | passlib 7 | -------------------------------------------------------------------------------- /roles/loki/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | roles: 5 | - role: grafana.grafana.loki 6 | -------------------------------------------------------------------------------- /roles/tempo/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | roles: 5 | - role: grafana.grafana.tempo 6 | -------------------------------------------------------------------------------- /roles/grafana/templates/tmpfiles.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | d {{ grafana_ini.server.socket | dirname }} 0775 grafana grafana 3 | -------------------------------------------------------------------------------- /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/mimir/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Restart mimir 4 | ansible.builtin.systemd: 5 | name: mimir.service 6 | state: restarted 7 | -------------------------------------------------------------------------------- /roles/tempo/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Restart tempo 4 | ansible.builtin.systemd: 5 | name: tempo.service 6 | state: restarted 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /roles/alloy/vars/Darwin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # macOS/Darwin specific variables 3 | __alloy_brew_tap: "grafana/grafana" 4 | __alloy_brew_package: "grafana/grafana/alloy" 5 | __alloy_config_path_default: "{{ __brew_prefix.stdout | default('/opt/homebrew') }}" 6 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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,<26.0.0" 9 | pylint = ">=2.16.2,<4.0.0" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = "3.10" 15 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/alloy/tasks/setup-RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: DNF - Install Alloy from remote URL 3 | ansible.builtin.package: 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/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/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/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/alloy/tasks/setup-Suse.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Zypper - Install Alloy from remote URL 3 | community.general.zypper: 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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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' -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/alloy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deploy alloy 3 | hosts: all 4 | become: true 5 | roles: 6 | - role: grafana.grafana.alloy 7 | vars: 8 | alloy_config: | 9 | prometheus.scrape "default" { 10 | targets = [{"__address__" = "127.0.0.1:12345"}] 11 | forward_to = [prometheus.remote_write.prom.receiver] 12 | } 13 | prometheus.remote_write "prom" { 14 | endpoint { 15 | url = "http://mimir:9009/api/v1/push" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | 12 | - name: Restart alloy macos 13 | listen: "restart alloy macos" 14 | ansible.builtin.command: "brew services restart {{ __alloy_brew_package }}" 15 | when: 16 | - not ansible_check_mode 17 | - ansible_facts['os_family'] == 'Darwin' 18 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | - name: macOS 23 | versions: 24 | - all 25 | - name: opensuse 26 | versions: 27 | - all 28 | galaxy_tags: 29 | - alloy 30 | - grafana 31 | - observability 32 | - monitoring 33 | - opentelemetry 34 | - telemetry 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | namespace: grafana 3 | name: grafana 4 | version: 6.0.6 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 | dependencies: 15 | community.general: '>=8.2.0' 16 | community.grafana: '>=1.5.4' 17 | ansible.posix: '>=1.5.4' 18 | repository: https://github.com/grafana/grafana-ansible-collection 19 | issues: https://github.com/grafana/grafana-ansible-collection/issues 20 | documentation: https://docs.ansible.com/ansible/latest/collections/grafana/grafana/index.html 21 | build_ignore: 22 | - node_modules/* 23 | -------------------------------------------------------------------------------- /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 | tags: 9 | - gitops 10 | links: 11 | - title: "Internal Slack Channel #ansible-collection" 12 | url: https://grafanalabs.enterprise.slack.com/archives/C04T7GX8C69 13 | annotations: 14 | backstage.io/techdocs-ref: dir:. 15 | github.com/project-slug: grafana/grafana-ansible-collection 16 | spec: 17 | type: tool 18 | owner: group:default/devex 19 | lifecycle: production 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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-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/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/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/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 | no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}" 21 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /.github/workflows/loki-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Loki Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'roles/loki/**' 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - 'roles/loki/**' 15 | 16 | defaults: 17 | run: 18 | working-directory: roles/loki 19 | 20 | jobs: 21 | molecule: 22 | name: Molecule 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | distro: 27 | - rockylinux9 28 | - ubuntu2204 29 | - debian12 30 | 31 | steps: 32 | - name: Check out the codebase. 33 | uses: actions/checkout@v4 34 | 35 | - name: Set up Python 3. 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: '3.x' 39 | 40 | - name: Install test dependencies. 41 | run: pip3 install ansible molecule molecule-plugins[docker] docker 42 | 43 | - name: Run Molecule tests. 44 | run: molecule test 45 | env: 46 | PY_COLORS: '1' 47 | ANSIBLE_FORCE_COLOR: '1' 48 | MOLECULE_DISTRO: ${{ matrix.distro }} 49 | -------------------------------------------------------------------------------- /.github/workflows/promtail-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Promtail Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'roles/promtail/**' 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - 'roles/promtail/**' 15 | 16 | defaults: 17 | run: 18 | working-directory: roles/promtail 19 | 20 | jobs: 21 | molecule: 22 | name: Molecule 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | distro: 27 | - rockylinux9 28 | - ubuntu2204 29 | - debian12 30 | 31 | steps: 32 | - name: Check out the codebase. 33 | uses: actions/checkout@v4 34 | 35 | - name: Set up Python 3. 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: '3.x' 39 | 40 | - name: Install test dependencies. 41 | run: pip3 install ansible molecule molecule-plugins[docker] docker 42 | 43 | - name: Run Molecule tests. 44 | run: molecule test 45 | env: 46 | PY_COLORS: '1' 47 | ANSIBLE_FORCE_COLOR: '1' 48 | MOLECULE_DISTRO: ${{ matrix.distro }} 49 | -------------------------------------------------------------------------------- /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/mimir-single-host.yaml: -------------------------------------------------------------------------------- 1 | - name: Install mimir 2 | hosts: monitoring-node 3 | become: true 4 | 5 | tasks: 6 | - name: Install mimir 7 | ansible.builtin.include_role: 8 | name: grafana.grafana.mimir 9 | vars: 10 | mimir_storage: 11 | storage: 12 | backend: s3 13 | s3: 14 | endpoint: "{{ s3_endpoint }}" 15 | access_key_id: "{{ vault_s3_access }}" 16 | secret_access_key: "{{ vault_s3_secret }}" 17 | bucket_name: your-mimir-bucket 18 | 19 | # Blocks storage requires a prefix when using a common object storage bucket. 20 | mimir_blocks_storage: 21 | storage_prefix: blocks 22 | tsdb: 23 | dir: "{{ mimir_working_path}}/ingester" 24 | 25 | mimir_limits: 26 | # set metrics retenion to 30d 27 | compactor_blocks_retention_period: 30d 28 | max_label_names_per_series: 100 29 | 30 | # this setting is required to prevent mimir from attempting 31 | # to make quorum 32 | mimir_ingester: 33 | ring: 34 | replication_factor: 1 35 | -------------------------------------------------------------------------------- /.github/workflows/alloy-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Alloy Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'roles/alloy/**' 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - 'roles/alloy/**' 15 | 16 | defaults: 17 | run: 18 | working-directory: roles/alloy 19 | 20 | jobs: 21 | molecule: 22 | name: Molecule 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | distro: 27 | - rockylinux9 28 | - ubuntu2204 29 | - debian12 30 | - opensuseleap15 31 | 32 | steps: 33 | - name: Check out the codebase. 34 | uses: actions/checkout@v4 35 | 36 | - name: Set up Python 3. 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: '3.x' 40 | 41 | - name: Install test dependencies. 42 | run: pip3 install ansible molecule molecule-plugins[docker] docker 43 | 44 | - name: Run Molecule tests. 45 | run: molecule test 46 | env: 47 | PY_COLORS: '1' 48 | ANSIBLE_FORCE_COLOR: '1' 49 | MOLECULE_DISTRO: ${{ matrix.distro }} 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /.github/workflows/opentelemetry-collector-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: OpenTelemetry Collector Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'roles/opentelemetry_collector/**' 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - 'roles/opentelemetry_collector/**' 15 | 16 | defaults: 17 | run: 18 | working-directory: roles/opentelemetry_collector 19 | 20 | jobs: 21 | molecule: 22 | name: Molecule 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | scenario: 27 | - default 28 | - default-check-first 29 | - latest 30 | - non-contrib 31 | 32 | steps: 33 | - name: Check out the codebase. 34 | uses: actions/checkout@v4 35 | 36 | - name: Set up Python 3. 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: '3.x' 40 | 41 | - name: Install test dependencies. 42 | run: pip3 install ansible molecule molecule-plugins[docker] docker pytest-testinfra 43 | 44 | - name: Run Molecule tests. 45 | run: molecule test -s ${{ matrix.scenario }} 46 | env: 47 | PY_COLORS: '1' 48 | ANSIBLE_FORCE_COLOR: '1' 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-{{ mimir_version }}/mimir-{{ mimir_version }}_{{ __mimir_arch }}.rpm" 7 | mimir_download_url_deb: "https://github.com/grafana/mimir/releases/download/mimir-{{ mimir_version }}/mimir-{{ 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 | 13 | arch_mapping: 14 | x86_64: amd64 15 | aarch64: arm64 16 | armv7l: armhf 17 | i386: i386 18 | ppc64le: ppc64le 19 | 20 | mimir_server: 21 | http_listen_port: "{{ mimir_http_listen_port }}" 22 | http_listen_address: "{{ mimir_http_listen_address }}" 23 | 24 | mimir_ruler: 25 | rule_path: "{{ mimir_working_path }}/ruler" 26 | alertmanager_url: "http://localhost:{{ mimir_http_listen_port }}/alertmanager" 27 | 28 | mimir_alertmanager: 29 | data_dir: "{{ mimir_working_path }}/alertmanager" 30 | external_url: "http://localhost:{{ mimir_http_listen_port }}/alertmanager" 31 | 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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') or [] }}" 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/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for alloy 3 | alloy_version: "latest" 4 | alloy_uninstall: false 5 | alloy_expose_port: false 6 | alloy_github_api_url: "https://api.github.com/repos/grafana/alloy/releases/latest" 7 | alloy_download_url_rpm: "https://github.com/grafana/alloy/releases/download/v{{ alloy_version }}/alloy-{{ alloy_version }}-1.{{ __alloy_arch }}.rpm" 8 | alloy_download_url_deb: "https://github.com/grafana/alloy/releases/download/v{{ alloy_version }}/alloy-{{ alloy_version }}-1.{{ __alloy_arch }}.deb" 9 | alloy_readiness_check_use_https: false 10 | alloy_readiness_check_use_proxy: true 11 | 12 | alloy_user_groups: [] 13 | # alloy_user_groups: 14 | # - "systemd-journal" 15 | 16 | alloy_env_file_vars: {} 17 | # alloy_env_file_vars: 18 | # CONFIG_FILE: "/custom/path" 19 | # CUSTOM_ARGS: "--server.http.listen-addr=0.0.0.0:12345 --stability.level=public-preview --feature.community-components.enabled=true" 20 | 21 | alloy_systemd_override: {} 22 | # alloy_systemd_override: | 23 | # [Service] 24 | # User=root 25 | 26 | alloy_config: {} 27 | # alloy_config: | 28 | # prometheus.scrape "default" { 29 | # targets = [{"__address__" = "127.0.0.1:12345"}] 30 | # forward_to = [prometheus.remote_write.prom.receiver] 31 | # } 32 | # prometheus.remote_write "prom" { 33 | # endpoint { 34 | # url = "http://mimir:9009/api/v1/push" 35 | # } 36 | # } 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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: "u=rwX,g=rX,o=rX" 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.github/workflows/mimir-molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mimir Molecule 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'roles/mimir/**' 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - 'roles/mimir/**' 15 | 16 | defaults: 17 | run: 18 | working-directory: roles/mimir 19 | 20 | jobs: 21 | molecule: 22 | name: Molecule 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | distro: 27 | - rockylinux9 28 | - rockylinux8 29 | - ubuntu2204 30 | - debian12 31 | 32 | steps: 33 | - name: Check out the codebase. 34 | uses: actions/checkout@v4 35 | 36 | - name: Set up Python 3. 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: '3.x' 40 | 41 | - name: Install test dependencies. 42 | run: pip3 install ansible-core==2.16 'molecule-plugins[docker]' pytest-testinfra jmespath selinux passlib 43 | 44 | - name: create docker network 45 | run: docker network create molecule 46 | 47 | - name: Start s3 backend 48 | 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" bitnamilegacy/minio:latest 49 | 50 | - name: Run Molecule tests. 51 | run: molecule --debug test 52 | env: 53 | PY_COLORS: '1' 54 | ANSIBLE_FORCE_COLOR: '1' 55 | MOLECULE_DISTRO: ${{ matrix.distro }} 56 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: 6.2.0 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/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: Stop Alloy service on macOS 9 | ansible.builtin.command: "brew services stop {{ __alloy_brew_package }}" 10 | when: ansible_facts['os_family'] == 'Darwin' 11 | failed_when: false 12 | 13 | - name: Uninstall Alloy rpm package 14 | ansible.builtin.package: 15 | name: "alloy" 16 | state: absent 17 | autoremove: true 18 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 19 | 20 | - name: Uninstall Alloy deb package 21 | ansible.builtin.apt: 22 | name: "alloy" 23 | state: absent 24 | purge: true 25 | when: ansible_facts['os_family'] == 'Debian' 26 | 27 | - name: Uninstall Alloy via Homebrew 28 | community.general.homebrew: 29 | name: "{{ __alloy_brew_package }}" 30 | state: absent 31 | when: ansible_facts['os_family'] == 'Darwin' 32 | 33 | - name: Uninstall Alloy rpm package (SUSE) 34 | community.general.zypper: 35 | name: "alloy" 36 | state: absent 37 | when: ansible_facts['os_family'] == 'Suse' 38 | 39 | - name: Ensure that Alloy firewalld rule is not present - tcp port {{ __alloy_server_http_listen_port }} 40 | ansible.posix.firewalld: # noqa ignore-errors 41 | immediate: true 42 | permanent: true 43 | port: "{{ __alloy_server_http_listen_port }}/tcp" 44 | state: disabled 45 | ignore_errors: true 46 | 47 | - name: Remove Alloy directories 48 | ansible.builtin.file: 49 | path: "{{ remove_me }}" 50 | state: absent 51 | loop: 52 | - "/etc/alloy" 53 | - "/etc/systemd/system/alloy.service.d" 54 | - "/var/lib/alloy" 55 | - "/etc/sysconfig/alloy" 56 | - "/etc/default/alloy" 57 | loop_control: 58 | loop_var: remove_me 59 | 60 | - name: Remove Alloy config directory on macOS 61 | ansible.builtin.file: 62 | path: "{{ __alloy_config_path_default }}" 63 | state: absent 64 | when: ansible_facts['os_family'] == 'Darwin' 65 | 66 | - name: Remove the Alloy system user 67 | ansible.builtin.user: 68 | name: "alloy" 69 | force: true 70 | state: absent 71 | 72 | - name: Remove Alloy system group 73 | ansible.builtin.group: 74 | name: "alloy" 75 | state: absent 76 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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 | 59 | {% if mimir_ingester is defined %} 60 | ingester: 61 | {{ mimir_ingester | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 62 | {% endif %} 63 | 64 | {% if mimir_querier is defined %} 65 | querier: 66 | {{ mimir_querier | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}} 67 | {% endif %} 68 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/alloy/tasks/setup-Darwin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check if Homebrew is installed 3 | ansible.builtin.command: which brew 4 | register: __brew_check 5 | changed_when: false 6 | failed_when: false 7 | 8 | - name: Fail if Homebrew is not installed 9 | ansible.builtin.fail: 10 | msg: "Homebrew is required but not installed" 11 | when: __brew_check.rc != 0 12 | 13 | - name: Get Homebrew prefix 14 | ansible.builtin.command: brew --prefix 15 | register: __brew_prefix 16 | changed_when: false 17 | 18 | - name: Set Alloy config directory path 19 | ansible.builtin.set_fact: 20 | __alloy_config_path: "{{ __alloy_config_path_default }}" 21 | 22 | - name: Add Grafana tap to Homebrew 23 | community.general.homebrew_tap: 24 | name: "{{ __alloy_brew_tap }}" 25 | state: present 26 | 27 | - name: Install Alloy via Homebrew 28 | community.general.homebrew: 29 | name: "{{ __alloy_brew_package }}" 30 | state: present 31 | update_homebrew: true 32 | 33 | - name: Ensure Alloy config directory exists 34 | ansible.builtin.file: 35 | path: "{{ __alloy_config_path }}" 36 | state: directory 37 | owner: "{{ ansible_user_id }}" 38 | group: "{{ ansible_user_gid }}" 39 | mode: '0755' 40 | 41 | - name: Template Alloy config 42 | ansible.builtin.template: 43 | src: "config.alloy.j2" 44 | dest: "{{ __alloy_config_path }}/config.alloy" 45 | owner: "{{ ansible_user_id }}" 46 | group: "{{ ansible_user_gid }}" 47 | mode: '0644' 48 | backup: true 49 | when: alloy_config | length > 0 50 | notify: restart alloy macos 51 | 52 | - name: Check if Alloy service is loaded 53 | ansible.builtin.command: brew services list 54 | register: __brew_services 55 | changed_when: false 56 | 57 | - name: Stop Alloy service if it exists (to clean up any issues) 58 | ansible.builtin.command: "brew services stop {{ __alloy_brew_package }}" 59 | register: __stop_result 60 | failed_when: false 61 | changed_when: "'Successfully stopped' in __stop_result.stdout" 62 | when: "'alloy' in __brew_services.stdout" 63 | 64 | - name: Start Alloy service 65 | ansible.builtin.command: "brew services start {{ __alloy_brew_package }}" 66 | when: 67 | - "'alloy' not in __brew_services.stdout or 'started' not in __brew_services.stdout" 68 | register: __service_start 69 | failed_when: __service_start.rc != 0 70 | 71 | - name: Restart Alloy service if already running 72 | ansible.builtin.command: "brew services restart {{ __alloy_brew_package }}" 73 | when: 74 | - "'alloy' in __brew_services.stdout and 'started' in __brew_services.stdout" 75 | register: __service_restart 76 | failed_when: __service_restart.rc != 0 77 | 78 | - name: Check final service status 79 | ansible.builtin.command: brew services list 80 | register: __final_brew_services 81 | changed_when: false 82 | 83 | - name: Verify Alloy installation 84 | ansible.builtin.command: alloy --version 85 | register: __alloy_version_output 86 | changed_when: false 87 | failed_when: false 88 | 89 | - name: Display Alloy version 90 | ansible.builtin.debug: 91 | msg: "Alloy version: {{ __alloy_version_output.stdout }}" 92 | when: __alloy_version_output.rc == 0 93 | 94 | - name: Display service status 95 | ansible.builtin.debug: 96 | msg: "Alloy service status: {{ __final_brew_services.stdout_lines | select('match', '.*alloy.*') | list }}" 97 | -------------------------------------------------------------------------------- /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 | check_mode: false 16 | 17 | - name: Latest available Tempo version 18 | ansible.builtin.set_fact: 19 | tempo_version: "{{ __github_latest_version.json.tag_name | regex_replace('^v?(\\d+\\.\\d+\\.\\d+)$', '\\1') }}" 20 | 21 | - name: Verify current deployed version 22 | block: 23 | - name: Check if Tempo binary is present 24 | ansible.builtin.stat: 25 | path: "/usr/bin/tempo" 26 | register: __already_deployed 27 | 28 | - name: Obtain current deployed Tempo version 29 | ansible.builtin.command: 30 | cmd: "/usr/bin/tempo --version" 31 | changed_when: false 32 | register: __current_deployed_version 33 | when: __already_deployed.stat.exists | bool 34 | 35 | - name: Include RedHat/Rocky setup 36 | ansible.builtin.include_tasks: 37 | file: setup-Redhat.yml 38 | when: ansible_facts['os_family'] in ['RedHat', 'Rocky'] 39 | 40 | - name: Include Debian/Ubuntu setup 41 | ansible.builtin.include_tasks: 42 | file: setup-Debian.yml 43 | when: ansible_facts['os_family'] == 'Debian' 44 | 45 | - name: Check if Tempo default dir is present 46 | ansible.builtin.stat: 47 | path: "/tmp/tempo/boltdb-shipper-active" 48 | register: __default_structure 49 | 50 | - name: Default structure cleanup 51 | when: __default_structure.stat.exists | bool 52 | block: 53 | - name: Ensure that Tempo is stopped before default cleanup 54 | ansible.builtin.systemd: 55 | name: tempo.service 56 | state: stopped 57 | 58 | - name: Remove default configuration from "/tmp/tempo" directory 59 | ansible.builtin.file: 60 | path: "/tmp/tempo" 61 | state: absent 62 | 63 | - name: Ensure that Tempo working path exists 64 | ansible.builtin.file: 65 | path: "{{ tempo_working_path }}" 66 | state: directory 67 | owner: "tempo" 68 | group: "root" 69 | mode: "0755" 70 | 71 | - name: Template Tempo config - /etc/tempo/config.yml 72 | ansible.builtin.template: 73 | src: "config.yml.j2" 74 | dest: "/etc/tempo/config.yml" 75 | owner: "tempo" 76 | group: "root" 77 | mode: "0644" 78 | validate: "/usr/bin/tempo -config.verify %s" 79 | notify: Restart tempo 80 | 81 | - name: Ensure restart has completed 82 | ansible.builtin.meta: flush_handlers 83 | 84 | - name: Ensure that Tempo is started 85 | ansible.builtin.systemd: 86 | name: tempo.service 87 | state: started 88 | enabled: true 89 | 90 | - name: Verify that Tempo URL is responding 91 | ansible.builtin.uri: 92 | url: "http://{{ tempo_http_listen_address }}:{{ tempo_http_listen_port }}/ready" 93 | method: GET 94 | register: tempo_verify_url_status_code 95 | retries: 5 96 | delay: 8 97 | until: tempo_verify_url_status_code.status == 200 98 | when: 99 | - not ansible_check_mode 100 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | environment: 90 | PATH: "/usr/local/bin:{{ ansible_env.PATH }}" 91 | notify: 92 | - Restart mimir 93 | 94 | - name: Ensure restart has completed 95 | ansible.builtin.meta: flush_handlers 96 | 97 | - name: Ensure that Mimir is started 98 | ansible.builtin.systemd: 99 | name: mimir.service 100 | state: started 101 | enabled: true 102 | 103 | 104 | - name: Verify that Mimir URL is responding 105 | ansible.builtin.uri: 106 | url: "http://{{ mimir_http_listen_address }}:{{ mimir_http_listen_port }}/ready" 107 | method: GET 108 | register: mimir_verify_url_status_code 109 | retries: 5 110 | delay: 8 111 | until: mimir_verify_url_status_code.status == 200 112 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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 | --------------------------------------------------------------------------------