Merge branch 'master' into role-mkfs

This commit is contained in:
Sergey Malyuk
2025-12-18 10:29:06 +03:00
249 changed files with 8437 additions and 1 deletions

View File

@@ -0,0 +1,53 @@
genlab.alertmanager
=========
The [Alertmanager](https://github.com/prometheus/alertmanager) handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts.
You'll need to manually specify the paths to your template files in the main Alertmanager configuration file (`alertmanager.yml`) on the target machine. The configuration file must have a `.yml` suffix, and template files must use a `.tmpl` suffix.
Requirements
------------
None
Role Variables
--------------
```
alertmanager_version: 0.28.1 # version of Alertmanager (AM)
alertmanager_config_dir: "/etc/alertmanager/conf" # where to place AM configs
alertmanager_storage_dir: "/var/lib/alertmanager" # path to AM storage
alertmanager_dir: "/etc/alertmanager" # where to install AM on target
alertmanager_user: "alertmanager" # system user name
alertmanager_group: "alertmanager" # system group name
config_source_dir: "./alertmanager" # path to config files on source
# Optional
template_source_dir: "./templates" # path to template files on source
```
Dependencies
------------
None
Example Playbook
----------------
```yaml
roles:
- role: genlab.template
config_source_dir: alertmanager
alertmanager_version: 0.28.1
```
License
-------
BSD
Author Information
------------------
corvus-migratorius@proton.me

View File

@@ -0,0 +1,7 @@
---
alertmanager_version: 0.28.1
alertmanager_config_dir: "/etc/alertmanager/conf"
alertmanager_storage_dir: "/var/lib/alertmanager"
alertmanager_dir: "/etc/alertmanager"
alertmanager_user: alertmanager
alertmanager_group: alertmanager

View File

@@ -0,0 +1,7 @@
---
- name: "(Re)start and enable Alertmanager"
ansible.builtin.systemd_service:
name: alertmanager.service
state: restarted
enabled: true
daemon_reload: true

View File

@@ -0,0 +1,17 @@
---
galaxy_info:
role_name: alertmanager
namespace: genlab
author: "Alexander Gorelyshev"
company: "Genlab, LLC"
description: ""
license: "MIT"
min_ansible_version: "2.1"
platforms:
- name: "Ubuntu"
versions: ["focal", "jammy", "noble"]
galaxy_tags: []
dependencies: []

View File

@@ -0,0 +1,36 @@
global:
# SMTP configuration for email notifications (if needed)
smtp_smarthost: 'mailserver.example.com:587'
smtp_from: 'alertmanager@example.com'
smtp_auth_username: 'alertmanager'
smtp_auth_password: 'your_password'
# Other global settings like resolve_timeout, http_config, etc.
route:
# Default receiver for alerts
receiver: 'default-receiver'
# Labels used for grouping alerts
group_by: ['alertname', 'instance', 'severity']
# Timing settings (group_wait, group_interval, repeat_interval)
# You can have nested 'routes' for more complex routing logic
receivers:
- name: 'default-receiver'
email_configs:
- to: 'ops-team@example.com'
inhibit_rules:
# Rules to suppress alerts based on other alerts
# Example:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
# Suppress 'warning' alerts if a 'critical' alert is also firing
templates:
# Paths to template files for customizing notifications
- '/etc/alertmanager/templates/*.tmpl'

View File

@@ -0,0 +1,6 @@
---
- name: Converge
hosts: all
roles:
- role: genlab.common.alertmanager
config_source_dir: alertmanager

View File

@@ -0,0 +1,27 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@@ -0,0 +1,55 @@
---
- name: Verify
hosts: all
gather_facts: false
any_errors_fatal: true
tasks:
- name: "Include default vars"
ansible.builtin.include_vars:
dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/"
extensions: ['yml']
- name: "Check if Alertmanager is installed"
changed_when: false
ansible.builtin.command: "alertmanager --version"
register: alertmanager_installed_version
- name: "Check Alertmanager version"
ansible.builtin.assert:
that: "alertmanager_installed_version.stdout is regex('{{ alertmanager_version }}')"
success_msg: "Alertmanager version {{ alertmanager_version }} is installed and working"
fail_msg: "Alertmanager version {{ alertmanager_version }} is not installed or not working correctly"
# kics-scan ignore-block
- name: "Check if Alertmanager is reachable"
ansible.builtin.uri:
url: "http://localhost:9093/-/healthy"
return_content: true
status_code: 200
method: GET
body_format: json
register: alertmanager_health
- name: "Assert Alertmanager health status"
ansible.builtin.assert:
that: "alertmanager_health.content == 'OK'"
success_msg: "Alertmanager is healthy"
fail_msg: "Alertmanager is not healthy"
# kics-scan ignore-block
- name: "Check if Alertmanager is ready"
ansible.builtin.uri:
url: "http://localhost:9093/-/ready"
return_content: true
status_code: 200
method: GET
body_format: json
register: alertmanager_ready
- name: "Assert Alertmanager readiness status"
ansible.builtin.assert:
that: "alertmanager_ready.content == 'OK'"
success_msg: "Alertmanager is ready"
fail_msg: "Alertmanager is not ready"

View File

@@ -0,0 +1,24 @@
---
- name: "Copy config file"
notify: "(Re)start and enable Alertmanager"
ansible.builtin.template:
src: "{{ item }}"
dest: "{{ alertmanager_config_dir }}/{{ item | basename }}"
owner: "{{ alertmanager_user }}"
group: "{{ alertmanager_group }}"
mode: "0660"
with_fileglob:
- "{{ config_source_dir }}/*.yml"
- name: "Copy templates if existed"
notify: "(Re)start and enable Alertmanager"
when: template_source_dir is defined
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ alertmanager_config_dir }}/{{ item | basename }}"
owner: "{{ alertmanager_user }}"
group: "{{ alertmanager_group }}"
mode: "0660"
with_fileglob:
- "{{ template_source_dir }}/*.tmpl"

View File

@@ -0,0 +1,85 @@
---
- name: "Create Alertmanager system group"
ansible.builtin.group:
name: "{{ alertmanager_user }}"
system: true
state: present
- name: "Create Alertmanager system user"
ansible.builtin.user:
name: "{{ alertmanager_user }}"
group: "{{ alertmanager_group }}"
system: true
shell: "/sbin/nologin"
create_home: false
state: present
- name: "Install Alertmanager from binary"
block:
- name: "Check Alertmanager version"
changed_when: false
ansible.builtin.command:
cmd: "alertmanager --version"
register: alertmanager_ver
- name: "Assert version correctness"
ansible.builtin.assert:
that: "alertmanager_ver.stdout is regex('{{ alertmanager_version }}')"
success_msg: "alertmanager version {{ alertmanager_version }} is installed and working"
fail_msg: "alertmanager version {{ alertmanager_version }} is not installed or not working correctly"
rescue:
- name: "Create Alertmanager directories '{{ item }}'"
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ alertmanager_user }}"
group: "{{ alertmanager_group }}"
mode: "0755"
with_items:
- "{{ alertmanager_config_dir }}"
- "{{ alertmanager_dir }}"
- "{{ alertmanager_storage_dir }}"
- name: "Download Alertmanager binary"
ansible.builtin.get_url:
url: "https://github.com/prometheus/alertmanager/releases/download/v{{ alertmanager_version }}/\
alertmanager-{{ alertmanager_version }}.linux-amd64.tar.gz"
dest: "/tmp/alertmanager-{{ alertmanager_version }}.linux-amd64.tar.gz"
owner: "{{ alertmanager_user }}"
group: "{{ alertmanager_group }}"
mode: "0644"
- name: "Unpack Alertmanager binaries"
notify: "(Re)start and enable Alertmanager"
ansible.builtin.unarchive:
src: "/tmp/alertmanager-{{ alertmanager_version }}.linux-amd64.tar.gz"
dest: "{{ alertmanager_dir }}"
creates: "{{ alertmanager_dir }}/alertmanager-{{ alertmanager_version }}.linux-amd64"
remote_src: true
- name: "Cleanup downloaded file"
ansible.builtin.file:
path: "/tmp/alertmanager-{{ alertmanager_version }}.linux-amd64.tar.gz"
state: absent
- name: "Move official alertmanager and amtool binaries"
ansible.builtin.copy:
src: "{{ alertmanager_dir }}/alertmanager-{{ alertmanager_version }}.linux-amd64/{{ item }}"
dest: "/usr/local/bin/{{ item }}"
mode: "0755"
owner: "{{ alertmanager_user }}"
group: "{{ alertmanager_group }}"
remote_src: true
with_items:
- alertmanager
- amtool
- name: "Create systemd service unit"
ansible.builtin.template:
src: alertmanager.service.j2
dest: /etc/systemd/system/alertmanager.service
owner: "{{ alertmanager_user }}"
group: "{{ alertmanager_group }}"
mode: "0660"

View File

@@ -0,0 +1,9 @@
---
- name: "Run installation tasks"
ansible.builtin.include_tasks: install.yml
- name: "Run configuration tasks"
ansible.builtin.include_tasks: configuration.yml
- name: "Flush handlers"
ansible.builtin.meta: "flush_handlers"

View File

@@ -0,0 +1,27 @@
[Unit]
Description=Alertmanager Service
After=network.target
Documentation="https://prometheus.io/docs/alerting/latest/alertmanager/"
[Service]
User={{ alertmanager_user }}
Group={{ alertmanager_group }}
Type=simple
ExecStart=/usr/local/bin/alertmanager \
--config.file={{ alertmanager_config_dir }}/alertmanager.yml \
--storage.path={{ alertmanager_storage_dir }}
Restart=on-failure
# Security hardening
ReadWritePaths={{ alertmanager_storage_dir }}
ProtectSystem=strict
NoNewPrivileges=true
PrivateTmp=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectClock=yes
RestrictSUIDSGID=true
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1 @@
---

40
roles/borgmatic/README.md Normal file
View File

@@ -0,0 +1,40 @@
ansible-borgmatic
=========
This is a wrapper around the `borgmatic` role from the `maxhoesel.borgbackup` collection.
The wrapper solve the most outstading issue with the current implementation of the original role: inability to install latest (or arbitrary) versions of `borgmatic` and `borg`.
In the case of Borg we are fetching a release from Github.
In the case of Borgmatic we are installing it via `pipx`, as recommended by their official documentation found here: https://torsion.org/borgmatic/docs/how-to/set-up-backups/.
Requirements
------------
- `maxhoesel.borgbackup` collection installed (see `requirements.yml`);
Role Variables
--------------
None
Dependencies
------------
None
Example Playbook
----------------
See `molecule/default/converge.yml`
License
-------
BSD
Author Information
------------------
corvus-migratorius@proton.me

View File

@@ -0,0 +1,18 @@
---
borgmatic_version: "1.4.0"
borgmatic_glibc_version: "2.36"
borgmatic_pipx_version: "1.7.1"
borgmatic_binary_url: "\
https://github.com/borgbackup/borg/releases/download/{{ borgmatic_version }}/borg-linux-glibc{{ borgmatic_glibc_version | replace('.', '') }}.tgz"
borgmatic_pipx_bin_dir: "/opt/borgmatic/bin"
borgmatic_schedule_oncalendar: "daily"
borgmatic_push_pubkey: true
borgmatic_sshkey_path: "/root/borgmatic/id_ed25519"
borgmatic_compression: "lz4"
borgmatic_keep_hourly: 0
borgmatic_keep_daily: 3
borgmatic_keep_weekly: 3
borgmatic_keep_monthly: 1
borgmatic_keep_yearly: 0
borgmatic_uptime_kuma:
borgmatic_loki:

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,17 @@
---
galaxy_info:
role_name: "borgmatic"
namespace: genlab
author: "Alexander Gorelyshev"
company: "Genlab, LLC"
description: ""
license: "MIT"
min_ansible_version: "2.1"
platforms:
- name: "Ubuntu"
versions: ["focal", "jammy", "noble"]
galaxy_tags: []
dependencies: []

View File

@@ -0,0 +1,48 @@
---
- name: Converge
hosts: all
vars:
repo_path: "/home/borg/test-repo"
pre_tasks:
- name: "Create a user for borg"
ansible.builtin.user:
name: borg
shell: /bin/bash
create_home: true
- name: "Generate test data file"
ansible.builtin.copy:
dest: "/tmp/data"
content: "This is a test file!"
owner: "{{ ansible_user_id }}"
group: "{{ ansible_user_id }}"
mode: "0644"
- name: "Ensure the repo path exists"
ansible.builtin.file:
path: "{{ repo_path }}"
state: directory
owner: "borg"
mode: "0700"
- name: "Install openssh-server"
ansible.builtin.apt:
name: openssh-server
state: present
update_cache: true
- name: "Start an SSH openssh-server"
ansible.builtin.systemd:
name: ssh
state: started
roles:
- role: genlab.common.borgmatic
borgmatic_source_directories:
- "/tmp/data"
borgmatic_repo_path: "ssh://borg@localhost/./test-repo"
borgmatic_repo_label: "test-repo"
borgmatic_encryption_passphrase: "secret"
repo_server_inventory_hostname: ubuntu # in production this should be an Ansible inventory hostname
repo_server_user: borg

View File

@@ -0,0 +1,31 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
scenario:
name: default
test_sequence:
- destroy
- create
- converge
# - idempotence
- verify

View File

@@ -0,0 +1,48 @@
---
- name: Verify
hosts: all
gather_facts: false
any_errors_fatal: true
vars:
repo_path: "/home/borg/test-repo"
tasks:
- name: "Include default vars"
ansible.builtin.include_vars:
dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/"
extensions: ['yml']
- name: "Check if Borg is installed"
changed_when: false
register: borgmatic_installed_version
ansible.builtin.command: "/usr/bin/borg --version"
- name: "Check Borg version"
ansible.builtin.assert:
that: borgmatic_installed_version.stdout.find(borgmatic_version)
success_msg: "borg version {{ borgmatic_version }} is installed and working"
fail_msg: "borg version {{ borgmatic_version }} is not installed or not working correctly"
- name: "Check if Borgmatic is installed"
changed_when: false
register: borgmatic_installed_version
ansible.builtin.command:
cmd: "/usr/bin/borgmatic --version"
- name: "Check that the test repo was created"
register: borgmatic_test_repo_readme
failed_when: borgmatic_test_repo_readme.stat.exists is false
ansible.builtin.stat:
path: "{{ repo_path }}"
- name: "Check that the systemd timer for Borgmatic is up and running"
register: borgmatic_timer
ansible.builtin.systemd:
name: borgmatic.timer
- name: "Assert that the timer is running"
ansible.builtin.assert:
that: borgmatic_timer.status.ActiveState == "active"
success_msg: "Timer is running"
fail_msg: "Unexpected timer state: '{{ borgmatic_timer.status.ActiveState }}'"

View File

@@ -0,0 +1,34 @@
---
- name: "Compose basic configuration for Borgmatic"
ansible.builtin.set_fact:
borgmatic_composite_config:
source_directories: "{{ borgmatic_source_directories }}"
repositories:
- path: "{{ borgmatic_repo_path }}"
label: "{{ borgmatic_repo_label }}"
encryption_passphrase: "{{ borgmatic_encryption_passphrase }}"
compression: "{{ borgmatic_compression }}"
# CLI output configuration
list_details: true
statistics: true
exclude_caches: true
# logging verbosity:
verbosity: 1
syslog_verbosity: 1
monitoring_verbosity: 1
# backup depth
keep_hourly: "{{ borgmatic_keep_hourly }}"
keep_daily: "{{ borgmatic_keep_daily }}"
keep_weekly: "{{ borgmatic_keep_weekly }}"
keep_monthly: "{{ borgmatic_keep_monthly }}"
keep_yearly: "{{ borgmatic_keep_yearly }}"
- name: "Add Uptime Kuma configuration"
when: borgmatic_uptime_kuma
ansible.builtin.set_fact:
borgmatic_composite_config: "{{ borgmatic_composite_config | combine({'uptime_kuma': borgmatic_uptime_kuma}) }}"
- name: "Add Loki configuration"
when: borgmatic_loki
ansible.builtin.set_fact:
borgmatic_composite_config: "{{ borgmatic_composite_config | combine({'loki': borgmatic_loki}) }}"

View File

@@ -0,0 +1,25 @@
---
- name: "Ensure the path for SSH keys exists"
ansible.builtin.file:
path: "{{ borgmatic_sshkey_path | dirname }}"
state: directory
owner: root
group: root
mode: "0700"
- name: "Generate an ed25519 SSH key pair with 100 KDF rounds"
register: borgmatic_ssh_key_pair
community.crypto.openssh_keypair:
type: ed25519
path: "{{ borgmatic_sshkey_path }}"
comment: "Generated by Ansible for Borgmatic"
force: false
mode: '0600'
- name: "Push the SSH key pair to the Borg repo host"
when: borgmatic_push_pubkey
delegate_to: "{{ repo_server_inventory_hostname }}"
ansible.posix.authorized_key:
user: "{{ repo_server_user }}"
key: "{{ borgmatic_ssh_key_pair.public_key }}"
state: present

View File

@@ -0,0 +1,66 @@
---
- name: "Ensure that system dependencies are installed"
ansible.builtin.apt:
name:
- openssh-client
- python3-pip
- python3-venv
state: present
update_cache: true
cache_valid_time: 3600
- name: "Install pipx"
retries: 3
delay: 1
ansible.builtin.pip:
name: "pipx=={{ borgmatic_pipx_version }}"
executable: pip3
break_system_packages: true
- name: "Ensure pipx binary is available in PATH"
changed_when: false
ansible.builtin.command:
cmd: pipx ensurepath
- name: "Install borgmatic via pipx"
retries: 3
delay: 1
environment:
PIPX_BIN_DIR: "{{ borgmatic_pipx_bin_dir }}"
community.general.pipx:
name: borgmatic
state: present
install_deps: true
- name: "Install Borg if the correct version is not available"
block:
# we are looking for Borg installed in a directory that Max Hoesel's role exects to find it
- name: "Get the currently installed version of Borg"
changed_when: false
register: borgmatic_version_installed
ansible.builtin.command:
cmd: /usr/bin/borg --version
- name: "Check that the correct version of Borg is installed"
ansible.builtin.assert:
that: borgmatic_version_installed.stdout.find(borgmatic_version)
fail_msg: "The expected Borg version was not found: {{ borgmatic_version_installed }}"
success_msg: "Found the expected Borg version ({{ borgmatic_version }})"
rescue:
- name: "Download Borg from a custom URL: '{{ borgmatic_binary_url }}'"
retries: 3
delay: 1
ansible.builtin.unarchive:
src: "{{ borgmatic_binary_url }}"
dest: "/opt/"
remote_src: true
owner: root
group: root
mode: "0755"
- name: "Create a symbolic link for Borg"
ansible.builtin.file:
state: link
src: "/opt/borg-dir/borg.exe"
dest: "/usr/bin/borg"

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,15 @@
---
- name: "Include tool installation tasks"
ansible.builtin.include_tasks: "install.yml"
- name: "Include SSH key handling tasks"
ansible.builtin.include_tasks: "handle-ssh-keys.yml"
- name: "Include configuration tasks"
ansible.builtin.include_tasks: "config.yml"
- name: "Include tasks for third-party integrations"
ansible.builtin.include_tasks: "integrations.yml"
- name: "Include tasks for running borgmatic"
ansible.builtin.include_tasks: "run.yml"

View File

@@ -0,0 +1,22 @@
---
# A workaround for maxhoesel.borgbackup.borgmatic that does not support custom paths
- name: "Create symbolic links for Borgmatic executables"
loop:
- borgmatic
- generate-borgmatic-config
- validate-borgmatic-config
ansible.builtin.file:
state: link
src: "{{ borgmatic_pipx_bin_dir }}/{{ item }}"
dest: /usr/bin/{{ item }}
- name: "Configure and run Borgmatic"
ansible.builtin.include_role:
name: maxhoesel.borgbackup.borgmatic
vars:
borgmatic_install: false # we handle installation separately to get the recent version
# borgmatic_ssh_key_gen_options: "-t ed25519 -a 100"
borgmatic_ssh_key_path: "{{ borgmatic_sshkey_path }}"
borgmatic_schedule_on: "{{ borgmatic_schedule_oncalendar }}"
borgmatic_schedule_max_random_delay: 600
borgmatic_config: "{{ borgmatic_composite_config }}"

View File

@@ -0,0 +1 @@
---

2
roles/curl_scheduled/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode
.idea

View File

@@ -0,0 +1,41 @@
curl-scheduled
=========
Configure curl to run on schedule by deploying a systemd service + timer. Useful for sending heartbeats.
Requirements
------------
None
Role Variables
--------------
- `args`: arguments to the curl command
- `url`: address to be accessed by curl
- `schedule`: string compatible with systemd timer `OnSchedule` option (default: `minutely`)
Dependencies
------------
None
Example Playbook
----------------
See `molecule/default/converge.yml`.
License
-------
BSD
Author Information
------------------
msayganova@genlab.llc
corvus-migratorius@proton.me

View File

@@ -0,0 +1,4 @@
---
curl_scheduled_curl_cmd: "/usr/bin/curl"
curl_scheduled_curl_args: "-fsS -m 10"
curl_scheduled_schedule: "minutely"

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,17 @@
---
galaxy_info:
role_name: curl_scheduled
namespace: genlab
author: "Alexander Gorelyshev"
company: "Genlab, LLC"
description: ""
license: "MIT"
min_ansible_version: "2.1"
platforms:
- name: "Ubuntu"
versions: ["focal", "jammy", "noble"]
galaxy_tags: []
dependencies: []

View File

@@ -0,0 +1,45 @@
---
- name: Converge
hosts: all
pre_tasks:
- name: "Create test directory"
ansible.builtin.file:
path: "/test"
state: directory
mode: "0664"
- name: "Create a test file"
ansible.builtin.lineinfile:
path: "/test/index.html"
create: true
mode: "0664"
line: "OK"
- name: "Simulate remote HTTP server(s) directly on localhost"
changed_when: false
async: 1
poll: 0
args:
chdir: "/test"
loop:
- 8080
- 8081
- 8082
ansible.builtin.shell:
cmd: nohup python3 -m http.server {{ item }} </dev/null >/dev/null 2>&1 &
executable: /bin/bash
roles:
- role: genlab.common.curl_scheduled
services:
- label: "localhost-test-zero"
url: "http://127.0.0.1:8080"
schedule: minutely
- label: "localhost-test-one"
url: "http://127.0.0.1:8081"
schedule: hourly
- label: "localhost-test-chained-curl"
curl_cmd: '/usr/bin/curl "http://127.0.0.1:8081" && /usr/bin/curl'
url: "http://127.0.0.1:8082"
schedule: minutely

View File

@@ -0,0 +1,27 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@@ -0,0 +1,81 @@
---
- name: Verify
hosts: all
gather_facts: false
tasks:
# Verify the systemd timers
- name: "Check the timer status - localhost-test-zero"
register: curl_scheduled_timer_zero
ansible.builtin.systemd:
name: "curl-localhost-test-zero.timer"
- name: "Assert that the localhost-test-zero timer is running"
ansible.builtin.assert:
that:
- curl_scheduled_timer_zero.status.ActiveState == "active"
success_msg: "Timer is running"
fail_msg: "Unexpected timer state: '{{ curl_scheduled_timer_zero.status.ActiveState }}'"
- name: "Check the timer status - localhost-test-one"
register: curl_scheduled_timer_one
ansible.builtin.systemd:
name: "curl-localhost-test-one.timer"
- name: "Assert that the localhost-test-one timer is running"
ansible.builtin.assert:
that:
- curl_scheduled_timer_one.status.ActiveState == "active"
success_msg: "Timer is running"
fail_msg: "Unexpected timer state: '{{ curl_scheduled_timer_one.status.ActiveState }}'"
- name: "Check the timer status - localhost-test-chained-curl"
register: curl_scheduled_timer_chained_curl
ansible.builtin.systemd:
name: "curl-localhost-test-chained-curl.timer"
- name: "Assert that the localhost-test-chained-curl timer is running"
ansible.builtin.assert:
that:
- curl_scheduled_timer_chained_curl.status.ActiveState == "active"
success_msg: "Timer is running"
fail_msg: "Unexpected timer state: '{{ curl_scheduled_timer_chained_curl.status.ActiveState }}'"
## Verify the systemd services
- name: "Check the service status - zero"
register: curl_scheduled_service_zero
ansible.builtin.systemd:
name: "curl-localhost-test-zero.service"
- name: "Assert that the localhost-test-zero service exited with a 0/SUCCESS status"
ansible.builtin.assert:
that:
- 'curl_scheduled_service_zero.status.ExecMainStatus == "0"'
success_msg: "Service has exited with a 0/SUCCESS status"
fail_msg: "Unexpected service status code: '{{ curl_scheduled_service_zero.status.ExecMainStatus }}'"
- name: "Check the service status - one"
register: curl_scheduled_service_one
ansible.builtin.systemd:
name: "curl-localhost-test-one.service"
- name: "Assert that the localhost-test-one service exited with a 0/SUCCESS status"
ansible.builtin.assert:
that:
- 'curl_scheduled_service_one.status.ExecMainStatus == "0"'
success_msg: "Service has exited with a 0/SUCCESS status"
fail_msg: "Unexpected service status code: '{{ curl_scheduled_service_one.status.ExecMainStatus }}'"
- name: "Check the service status - localhost-test-chained-curl"
register: curl_scheduled_service_chained_curl
ansible.builtin.systemd:
name: "curl-localhost-test-chained-curl.service"
- name: "Assert that the localhost-test-chained-curl service exited with a 0/SUCCESS status"
ansible.builtin.assert:
that:
- 'curl_scheduled_service_chained_curl.status.ExecMainStatus == "0"'
success_msg: "Service has exited with a 0/SUCCESS status"
fail_msg: "Unexpected service status code: '{{ curl_scheduled_service_chained_curl.status.ExecMainStatus }}'"

View File

@@ -0,0 +1,33 @@
---
- name: "Deploy-Service | Create systemd service file: '{{ job.label }}'"
vars:
label: "{{ job.label }}"
url: "{{ job.url }}"
curl_cmd: "{{ job.curl_cmd | default(curl_scheduled_curl_cmd) }}"
curl_args: "{{ job.curl_args | default(curl_scheduled_curl_args) }}"
register: curl_scheduled_service
ansible.builtin.template:
src: "placeholder.service"
dest: "/etc/systemd/system/curl-{{ job.label }}.service"
mode: "0660"
validate: systemd-analyze verify %s
- name: "Deploy-Service | Create systemd timer file: '{{ job.label }}'"
vars:
label: "{{ job.label }}"
schedule: "{{ job.schedule | default(curl_scheduled_schedule) }}"
register: curl_scheduled_timer
ansible.builtin.template:
src: "placeholder.timer"
dest: "/etc/systemd/system/curl-{{ job.label }}.timer"
mode: "0660"
validate: systemd-analyze verify %s
- name: "Deploy-Service | Enable and start the timer: '{{ job.label }}'" # noqa: no-handler
become: true
when: curl_scheduled_service.changed or curl_scheduled_timer.changed
ansible.builtin.systemd:
name: "curl-{{ job.label }}.timer"
state: started
enabled: true
daemon_reload: true

View File

@@ -0,0 +1,19 @@
---
- name: "Install curl (Debian derivatives)"
when: ansible_os_family == "Debian"
ansible.builtin.apt:
name: curl
state: present
update_cache: true
- name: "Install curl (RHEL derivatives)"
when: ansible_os_family == "RedHat"
ansible.builtin.dnf:
name: curl
state: present
- name: "Configure and deploy systemd service"
loop: "{{ services }}"
loop_control:
loop_var: "job"
ansible.builtin.include_tasks: "deploy-service.yml"

View File

@@ -0,0 +1,21 @@
[Unit]
Description=Run an HTTP request via curl designated '{{ label }}'
After=network.target
[Service]
Type=oneshot
ExecStart={{ curl_cmd }} {{ curl_args }} "{{ url }}"
Restart=no
# Security hardening
ProtectSystem=strict
NoNewPrivileges=true
PrivateTmp=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectClock=yes
RestrictSUIDSGID=true
[Install]
WantedBy=multi-user.targer

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Trigger a curl command designated '{{ label }}'
Requires=curl-{{ label }}.service
[Timer]
Unit=curl-{{ label }}.service
OnCalendar={{ schedule }}
AccuracySec=1m
Persistent=true
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,2 @@
---
curl_scheduled_kuma_port: "3001"

37
roles/dnsmasq/README.md Normal file
View File

@@ -0,0 +1,37 @@
ansible-dnsmasq
=========
Deploy dnsmasq on the target node. For now, supports only DNS functionality (DHCP and TFTP are not configuratble).
Requirements
------------
Take care to open the port you choose for dnsmasq to serve queries on. This role does not handle firewall configuration.
Role Variables
--------------
None
Dependencies
------------
None
Example Playbook
----------------
```yaml
roles:
- role: genlab.dnsmasq
```
License
-------
BSD
Author Information
------------------
corvus-migratorius@proton.me

View File

@@ -0,0 +1,3 @@
---
dnsmasq_cache_size: 100
dnsmasq_dns_port: 5300

View File

@@ -0,0 +1,7 @@
---
- name: "Restart dnsmasq"
ansible.builtin.systemd_service:
name: dnsmasq
state: restarted
daemon_reload: true
enabled: true

View File

@@ -0,0 +1,17 @@
---
galaxy_info:
role_name: dnsmasq
namespace: genlab
author: "Alexander Gorelyshev"
company: "Genlab, LLC"
description: ""
license: "MIT"
min_ansible_version: "2.1"
platforms:
- name: "Ubuntu"
versions: ["focal", "jammy"]
galaxy_tags: []
dependencies: []

View File

@@ -0,0 +1,21 @@
---
- name: Converge
hosts: all
roles:
- role: genlab.common.ufw
disable_ipv6: true
rules:
- rule: allow
# proto: udp
port: 5300
interface: lo
direction: in
comment: "Allow dnsmasq to serve DNS queries on the given interface"
- role: genlab.common.dnsmasq
dnsmasq_iface: lo
dnsmasq_domain: adm.local
dnsmasq_dns_port: 5300
dnsmasq_nodes:
- name: hub
ip: 127.0.0.1

View File

@@ -0,0 +1,27 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2204}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@@ -0,0 +1,19 @@
---
- name: Verify
hosts: all
gather_facts: false
any_errors_fatal: true
pre_tasks:
- name: "Install a package providing the `dig` tool"
ansible.builtin.apt:
name: dnsutils
state: present
tasks:
- name: "Test the output of the `dig` command"
changed_when: false
register: dnsmasq_dig
failed_when: 'dnsmasq_dig.stdout != "127.0.0.1"'
ansible.builtin.command:
cmd: "dig @127.0.0.1 -p 5300 hub.adm.local +short"

View File

@@ -0,0 +1,38 @@
---
- name: "Install dnsmasq"
ansible.builtin.apt:
name: dnsmasq
state: present
update_cache: true
- name: "Create interface-specific configuration file"
notify: "Restart dnsmasq"
ansible.builtin.blockinfile:
path: /etc/dnsmasq.d/{{ dnsmasq_domain }}.conf
create: true
owner: root
group: root
mode: "0660"
block: |
interface="{{ dnsmasq_iface }}"
port="{{ dnsmasq_dns_port }}"
cache-size="{{ dnsmasq_cache_size }}"
log-queries
server=1.1.1.1 # Cloudflare DNS
server=8.8.8.8 # Google DNS
server=8.8.4.4 # Google DNS (secondary)
- name: "Add dnsmasq_nodes to the configuration file"
notify: "Restart dnsmasq"
loop: "{{ dnsmasq_nodes }}"
ansible.builtin.blockinfile:
path: /etc/dnsmasq.d/{{ dnsmasq_domain }}.conf
marker: "# {mark} ANSIBLE MANAGED: {{ item.name }}.{{ dnsmasq_domain }}"
block: |
address=/{{ item.name }}.{{ dnsmasq_domain }}/{{ item.ip }}
- name: "Flush handlers"
ansible.builtin.meta: flush_handlers

View File

@@ -0,0 +1 @@
---

107
roles/grafana/README.md Normal file
View File

@@ -0,0 +1,107 @@
genlab.grafana
=========
This ansible role installs [Grafana](https://github.com/grafana/grafana) - the open-source platform for monitoring and observability. It can produce charts, graphs, and alerts for the web when connected to supported data sources.
This role installs and configures Grafana from a binary distribution. It also:
- Uploads custom dashboards
- Installs plugins
- Imports public dashboards and data sources
- Allows admin password changes
Supports user creation
Grafana service requires an environment file at startup, where you can set custom paths for logs (`grafana_log_dir`), data (`grafana_data_dir`), and plugins (`grafana_plugins_dir`). This allows flexible control over Grafana's data storage.
Requirements
------------
You need `community.grafana` module.
Role Variables
--------------
```
grafana_user: "grafana" # user name
grafana_group: "grafana" # group name
grafana_version: 11.5.0 # version
grafana_port: 3000 # port
# Directory paths
grafana_dashboard_dir: "/tmp/grafana/dashboards" # where to copy dashboards from source
grafana_plugins_dir: "/var/lib/grafana/plugins" # where to store plugins on target
grafana_datasource_dir: "/etc/grafana/provisioning/datasources" # where to store data sources on target
grafana_log_dir: "/var/log/grafana" # where to write logs
grafana_data_dir: "/var/lib/grafana" # where to store Grafana DB
# Optional configurations
grafana_users: [] # array of user names, passwords, and statuses
grafana_plugins: [] # array of plugins to install
grafana_public_dashboards: [] # array of public dashboards to import
# Admin credentials
admin_api_username: "secret" # Grafana admin username
admin_api_password: "secret" # Grafana admin password
# Source paths
dashboard_source_path: "mydir/dashboards" # path to dashboards on source server
datasource_source_path: "mydir/datasources" # path to data sources on source server
```
Dependencies
------------
None
Example Playbook
----------------
```yaml
---
- name: Converge
hosts: all
vars:
grafana_users:
- name: "test"
user_login: "test"
user_password: "test"
user_email: "test@mail.ru"
is_admin: false
grafana_plugins:
version: 2.1.8
- name: aceiot-svg-panel
version: 0.1.5
grafana_public_dashboards:
- name: Node Full Exporter
id: 1860
revision: 36
vars_files:
- secrets/admin_cred.yml
roles:
- role: genlab.grafana
grafana_version: 11.5.0
admin_api_username: "{{ grafana.admin_api_username }}"
admin_api_password: "{{ grafana.admin_api_password }}"
users: "{{ grafana_users }}"
plugins: "{{ grafana_plugins }}"
public_dashboards: "{{ grafana_public_dashboards }}"
dashboard_source_path: "molecule/default/dashboards"
datasource_source_path: "molecule/default/datasources"
grafana_log_dir: "/opt/grafana/data"
grafana_data_dir: "/opt/grafana/lib"
```
License
-------
BSD
Author Information
------------------
corvus-migratorius@proton.me

View File

@@ -0,0 +1,10 @@
---
grafana_user: "grafana"
grafana_group: "grafana"
grafana_version: 11.5.0
grafana_port: 3000
grafana_dashboard_dir: "/tmp/grafana/dashboards"
grafana_plugins_dir: "/var/lib/grafana/plugins"
grafana_datasource_dir: "/etc/grafana/provisioning/datasources"
grafana_log_dir: "/var/log/grafana"
grafana_data_dir: "/var/lib/grafana"

View File

@@ -0,0 +1,7 @@
---
- name: "(Re)start and enable Grafana"
ansible.builtin.systemd_service:
name: grafana-server
state: restarted
enabled: true
daemon_reload: true

View File

@@ -0,0 +1,17 @@
---
galaxy_info:
role_name: "grafana"
namespace: genlab
author: "Alexander Gorelyshev"
company: "Genlab, LLC"
description: ""
license: "MIT"
min_ansible_version: "2.1"
platforms:
- name: "Ubuntu"
versions: ["focal", "jammy", "noble"]
galaxy_tags: []
dependencies: []

View File

@@ -0,0 +1,44 @@
---
- name: Converge
hosts: all
vars:
grafana_users:
- name: "test"
user_login: "test"
# kics-scan ignore-line
user_password: "test"
user_email: "test@mail.ru"
is_admin: false
- name: "test2"
user_login: "test2"
# kics-scan ignore-line
user_password: "test2"
user_email: "test2@mail.ru"
is_admin: true
grafana_plugins:
- name: grafana-metricsdrilldown-app
version: 1.0.0
- name: grafana-clock-panel
version: 2.1.8
- name: aceiot-svg-panel
version: 0.1.5
grafana_public_dashboards:
- name: Node Full Exporter
id: 1860
revision: 36
vars_files:
- secrets/admin_cred.yml
roles:
# kics-scan ignore-block
- role: genlab.common.grafana
grafana_version: 11.5.0
admin_api_username: "{{ grafana.admin_api_username }}"
admin_api_password: "{{ grafana.admin_api_password }}"
users: "{{ grafana_users }}"
plugins: "{{ grafana_plugins }}"
public_dashboards: "{{ grafana_public_dashboards }}"
dashboard_source_path: "molecule/default/dashboards"
datasource_source_path: "molecule/default/datasources"
grafana_log_dir: "/opt/grafana/data"
grafana_data_dir: "/opt/grafana/lib"

View File

@@ -0,0 +1,700 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__requires": [
{
"type": "panel",
"id": "bargauge",
"name": "Bar gauge",
"version": ""
},
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "7.1.5"
},
{
"type": "panel",
"id": "graph",
"name": "Graph",
"version": ""
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "stat",
"name": "Stat",
"version": ""
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 29,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"mappings": [],
"min": 0,
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "#EAB839",
"value": 1
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 15,
"x": 0,
"y": 0
},
"id": 4,
"options": {
"displayMode": "gradient",
"minVizHeight": 10,
"minVizWidth": 0,
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"showUnfilled": true,
"valueMode": "color"
},
"pluginVersion": "10.1.5",
"targets": [
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", namespace=~\"$namespace\", status=~\"fail|error\" } > 0) by (namespace)",
"instant": true,
"interval": "",
"legendFormat": "{{namespace}}",
"refId": "A"
}
],
"title": "Failing Policies by Namespace",
"type": "bargauge"
},
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"mappings": [],
"min": 0,
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "#EAB839",
"value": 3
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 9,
"x": 15,
"y": 0
},
"id": 5,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "center",
"orientation": "vertical",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.1.5",
"targets": [
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(cluster_policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", status=~\"fail|error\" } > 0) by (status)",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
"legendFormat": "{{ status }}",
"refId": "A"
}
],
"title": "Failing ClusterPolicies",
"type": "stat"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 8
},
"hiddenSeries": false,
"id": 11,
"legend": {
"alignAsTable": true,
"avg": false,
"current": true,
"hideEmpty": true,
"hideZero": true,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "10.1.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(cluster_policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", status=~\"fail|error\" } > 0) by (policy)",
"interval": "",
"legendFormat": "{{ policy }}",
"refId": "A"
},
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", namespace=~\"$namespace\", status=~\"fail|error\" } > 0) by (policy)",
"interval": "",
"legendFormat": "{{ policy }}",
"refId": "B"
}
],
"thresholds": [],
"timeRegions": [],
"title": "Failing Policies Graph",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"decimals": 0,
"format": "short",
"logBase": 1,
"min": "0",
"show": true
},
{
"format": "short",
"logBase": 1,
"show": true
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 18
},
"id": 7,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "10.1.5",
"targets": [
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", namespace=~\"$namespace\", status=~\"fail|error\" }) by (namespace,policy,rule,kind,name,status,category,severity,source)",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "{{namespace}}: {{ policy }}",
"refId": "A"
}
],
"title": "Failing PolicyRules",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"Value": true
},
"indexByName": {
"category": 1,
"kind": 4,
"name": 5,
"namespace": 3,
"policy": 6,
"rule": 7,
"severity": 2,
"source": 0,
"status": 8
},
"renameByName": {
"namespace": "namespace"
}
}
}
],
"type": "table"
},
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"custom": {
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 28
},
"id": 9,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "10.1.5",
"targets": [
{
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(cluster_policy_report_result{policy=~\"$policy\", category=~\"$category\", severity=~\"$severity\", source=~\"$source\", kind=~\"$kind\", status=~\"fail|error\" }) by (policy,rule,kind,name,status,category,severity,source)",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "{{ kind }}: {{ name }} - {{ policy }}",
"refId": "A"
}
],
"title": "Failing ClusterPolicyRules",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"Value": true,
"__name__": true,
"container": true,
"endpoint": true,
"instance": true,
"job": true,
"namespace": true,
"pod": true,
"report": true,
"service": true
},
"indexByName": {
"category": 1,
"kind": 3,
"name": 4,
"policy": 5,
"rule": 6,
"severity": 2,
"source": 0,
"status": 7
},
"renameByName": {}
}
}
],
"type": "table"
}
],
"refresh": "",
"schemaVersion": 38,
"style": "dark",
"tags": [
"Policy Reporter"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "prometheus"
},
"hide": 0,
"includeAll": false,
"label": "Datasource",
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, policy)",
"hide": 0,
"includeAll": true,
"label": "Policy",
"multi": true,
"name": "policy",
"options": [],
"query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, policy)",
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, category)",
"hide": 0,
"includeAll": true,
"label": "Category",
"multi": true,
"name": "category",
"options": [],
"query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, category)",
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, severity)",
"hide": 0,
"includeAll": true,
"label": "Severity",
"multi": true,
"name": "severity",
"options": [],
"query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, severity)",
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values({__name__= \"policy_report_result\", status=~\"fail|error\"}, namespace)",
"hide": 0,
"includeAll": true,
"label": "Namespace",
"multi": true,
"name": "namespace",
"options": [],
"query": "label_values({__name__= \"policy_report_result\", status=~\"fail|error\"}, namespace)",
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, kind)",
"hide": 0,
"includeAll": true,
"label": "Kind",
"multi": true,
"name": "kind",
"options": [],
"query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, kind)",
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": {
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, source)",
"hide": 0,
"includeAll": true,
"label": "Source",
"multi": true,
"name": "source",
"options": [],
"query": "label_values({__name__=~ \"policy_report_result|cluster_policy_report_result\", status=~\"fail|error\"}, source)",
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "PolicyReports",
"uid": "ZkwXrUMnk",
"version": 1,
"gnetId": 13968
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
basicAuth: false
basicAuthPassword: false
url: http://localhost:3100
jsonData:
maxLines: 1000
httpHeaderName1: "Authorization"
secureJsonData:
httpHeaderValue1: "Bearer your_token_here"
version: 1
editable: false

View File

@@ -0,0 +1,50 @@
# config file version
apiVersion: 1
# list of datasources that should be deleted from the database
# deleteDatasources:
# - name: Prometheus
# orgId: 1
# list of datasources to insert/update depending
# whats available in the database
datasources:
# <string, required> name of the datasource. Required
- name: Prometheus
# <string, required> datasource type. Required
type: prometheus
# <string, required> access mode. direct or proxy. Required
access: proxy
# <int> org id. will default to orgId 1 if not specified
orgId: 1
# <string> url
url: http://localhost:9090
# <string> database password, if used
password:
# <string> database user, if used
user:
# <string> database name, if used
database:
# <bool> enable/disable basic auth
basicAuth: false
# <string> basic auth username, if used
basicAuthUser:
# <string> basic auth password, if used
basicAuthPassword:
# <bool> enable/disable with credentials headers
withCredentials:
# <bool> mark as default datasource. Max one per org
isDefault: true
# <map> fields that will be converted to json and stored in json_data
jsonData:
graphiteVersion: "1.1"
tlsAuth: false
tlsAuthWithCACert: false
# <string> json object of data that will be encrypted.
secureJsonData:
tlsCACert: "..."
tlsClientCert: "..."
tlsClientKey: "..."
version: 1
# <bool> allow users to edit datasources from the UI.
editable: true

View File

@@ -0,0 +1,29 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
published_ports:
- 127.0.0.1:3001:3000
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@@ -0,0 +1,3 @@
grafana:
admin_api_username: "admin"
admin_api_password: "changme23"

View File

@@ -0,0 +1,41 @@
---
- name: Verify
hosts: all
gather_facts: false
any_errors_fatal: true
vars_files:
- admin_cred.yml
tasks:
- name: "Include default vars"
ansible.builtin.include_vars:
dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/"
extensions: ['yml']
- name: "Check if Grafana is installed"
changed_when: false
ansible.builtin.command: "grafana-server -v"
register: grafana_installed_version
- name: "Check Grafana version"
ansible.builtin.assert:
that: "grafana_installed_version.stdout is regex('{{ grafana_version }}')"
success_msg: "grafana version {{ grafana_version }} is installed and working"
fail_msg: "grafana version {{ grafana_version }} is not installed or not working correctly"
# kics-scan ignore-block
- name: "Check if Grafana login page is reachable"
ansible.builtin.uri:
url: "http://localhost:{{ grafana_port }}/api/health"
return_content: true
status_code: 200
method: GET
body_format: json
register: grafana_health
- name: "Debug Grafana health status"
ansible.builtin.assert:
that: grafana_health.json.database == "ok"
success_msg: "Grafana is healthy"
fail_msg: "Grafana is not healthy"

View File

@@ -0,0 +1,40 @@
---
- name: "Create dashboard directory"
ansible.builtin.file:
path: "{{ grafana_dashboard_dir }}"
state: directory
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
mode: "0755"
- name: "Copy dashboard files from source to target"
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ grafana_dashboard_dir }}/{{ item | basename }}"
mode: "0644"
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
with_fileglob:
- "{{ dashboard_source_path }}/*.json"
- name: "Import Grafana dashboards to Grafana"
community.grafana.grafana_dashboard:
grafana_url: "http://127.0.0.1:{{ grafana_port }}"
url_username: "{{ admin_api_username }}"
url_password: "{{ admin_api_password }}"
state: present
commit_message: Updated by ansible
overwrite: false
path: "{{ grafana_dashboard_dir }}/{{ item | basename }}"
with_fileglob:
- "{{ dashboard_source_path }}/*.json"
- name: "Run | reload Grafana provisioned dashboard configurations"
ansible.builtin.uri:
# kics-scan ignore-line
url: "http://127.0.0.1:{{ grafana_port }}/api/admin/provisioning/dashboards/reload"
method: POST
force_basic_auth: true
user: "{{ admin_api_username }}"
password: "{{ admin_api_password }}"
status_code: 200

View File

@@ -0,0 +1,28 @@
---
- name: "Create datasource directory"
ansible.builtin.file:
path: "{{ grafana_datasource_dir }}"
state: directory
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
mode: "0755"
- name: "Configure | provision datasources for Grafana"
ansible.builtin.copy:
src: "{{ datasource_source_path }}/{{ item | basename }}"
dest: "{{ grafana_datasource_dir }}/{{ item | basename }}"
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
mode: "0660"
with_fileglob:
"{{ datasource_source_path }}/*.y*ml"
- name: "Run | reload Grafana datasource provisioned configurations"
ansible.builtin.uri:
# kics-scan ignore-line
url: "http://127.0.0.1:{{ grafana_port }}/api/admin/provisioning/datasources/reload"
method: POST
force_basic_auth: true
user: "{{ admin_api_username }}"
password: "{{ admin_api_password }}"
status_code: 200

View File

@@ -0,0 +1,11 @@
---
- name: "Import public dashboard - '{{ public_dashboard.name }}''"
community.grafana.grafana_dashboard:
grafana_url: "http://127.0.0.1:{{ grafana_port }}"
state: "{{ public_dashboard.state | default('present') }}"
overwrite: false
dashboard_id: "{{ public_dashboard.id }}"
dashboard_revision: "{{ public_dashboard.revision }}"
commit_message: "Add public dashboard '{{ public_dashboard.name }}''"
url_username: "{{ admin_api_username }}"
url_password: "{{ admin_api_password }}"

View File

@@ -0,0 +1,78 @@
---
- name: "Create Grafana system group"
ansible.builtin.group:
name: grafana
system: true
state: present
- name: "Create Grafana system user"
ansible.builtin.user:
name: grafana
group: grafana
system: true
shell: "/sbin/nologin"
create_home: false
state: present
- name: "Install Grafana deb package"
block:
- name: "Check Grafana version"
changed_when: false
ansible.builtin.command:
cmd: "grafana-server --version"
register: grafana_ver
- name: "Assert version correctness"
ansible.builtin.assert:
that: "grafana_ver.stdout is regex('{{ grafana_version }}')"
success_msg: "grafana version {{ grafana_version }} is installed and working"
fail_msg: "grafana version {{ grafana_version }} is not installed or not working correctly"
rescue:
- name: "Ensure that directories exist"
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
mode: '0775'
with_items:
- "{{ grafana_log_dir }}"
- "{{ grafana_data_dir }}"
- "{{ grafana_plugins_dir }}"
- name: "Download Grafana DEB package"
ansible.builtin.get_url:
url: "https://dl.grafana.com/oss/release/grafana_{{ grafana_version }}_amd64.deb"
dest: "/tmp/grafana-{{ grafana_version }}_amd64.deb"
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
mode: "0644"
register: grafana_package_tmp
- name: "Install DEB package"
notify: "(Re)start and enable Grafana"
ansible.builtin.apt:
deb: "{{ grafana_package_tmp.dest }}"
state: present
update_cache: true
- name: "Cleanup downloaded file"
ansible.builtin.file:
path: "/tmp/grafana-{{ grafana_version }}_amd64.deb"
state: absent
- name: "Create env file for systemd service unit"
notify: "(Re)start and enable Grafana"
ansible.builtin.template:
src: grafana-server.env.j2
dest: "{{ item }}"
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
mode: "0660"
with_items:
- "/etc/default/grafana-server"
- "/etc/default/grafana"
- name: "Flush handlers"
ansible.builtin.meta: "flush_handlers"

View File

@@ -0,0 +1,39 @@
---
- name: "Include grafana installation tasks"
ansible.builtin.include_tasks: install.yml
- name: "Wait for the Grafana server to become available"
ansible.builtin.wait_for:
host: "127.0.0.1"
port: "{{ grafana_port }}"
state: started
delay: 10
- name: "Include user creation tasks"
when: users is defined
loop: "{{ users }}"
loop_control:
loop_var: user
ansible.builtin.include_tasks: user.yml
- name: "Configure custom dashboards"
when: dashboard_source_path is defined
ansible.builtin.include_tasks: dashboards.yml
- name: "Configure public dashboards"
when: public_dashboards is defined
loop: "{{ public_dashboards }}"
loop_control:
loop_var: public_dashboard
ansible.builtin.include_tasks: import_pub_dashboard.yml
- name: "Configure plugins"
when: plugins is defined
loop: "{{ plugins }}"
loop_control:
loop_var: plugin
ansible.builtin.include_tasks: plugins.yml
- name: "Configure datasources"
when: datasource_source_path is defined
ansible.builtin.include_tasks: datasources.yml

View File

@@ -0,0 +1,16 @@
---
- name: "Create plugin directory"
ansible.builtin.file:
path: "{{ grafana_plugins_dir }}"
state: directory
owner: "{{ grafana_user }}"
group: "{{ grafana_group }}"
mode: "0755"
- name: "Install Grafana plugins"
community.grafana.grafana_plugin:
name: "{{ plugin.name }}"
version: "{{ plugin.version }}"
grafana_plugins_dir: "{{ grafana_plugins_dir }}"
state: "{{ plugin.state | default('present') }}"
notify: "(Re)start and enable Grafana"

View File

@@ -0,0 +1,19 @@
---
- name: "Reset default admin password"
ansible.builtin.command: >
grafana-cli admin reset-admin-password "{{ admin_api_password }}"
no_log: true
changed_when: false
- name: "Create | update a Grafana user"
community.grafana.grafana_user:
url: "http://127.0.0.1:{{ grafana_port }}"
url_username: "{{ admin_api_username }}"
url_password: "{{ admin_api_password }}"
name: "{{ user.name }}"
email: "{{ user.user_email }}"
login: "{{ user.user_login }}"
password: "{{ user.user_password }}"
is_admin: "{{ user.is_admin | default(false) }}"
state: present

View File

@@ -0,0 +1,24 @@
GRAFANA_USER=grafana
GRAFANA_GROUP=grafana
GRAFANA_HOME=/usr/share/grafana
LOG_DIR={{ grafana_log_dir }}
DATA_DIR={{ grafana_data_dir }}
MAX_OPEN_FILES=10000
CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true
PLUGINS_DIR={{ grafana_plugins_dir }}
PROVISIONING_CFG_DIR=/etc/grafana/provisioning
# Only used on systemd systems
PID_FILE_DIR=/run/grafana

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,61 @@
genlab.ipmi_exporter
=========
This Ansible role installs ipmi_exporter on target host. This is a Prometheus exporter for Intelligent Platform Management Interface [metrics](https://github.com/prometheus-community/ipmi_exporter/blob/master/docs/metrics.md)
Requirements
------------
By default, the exporter relies on tools from the FreeIPMI suite for the actual IPMI implementation.
Role Variables
--------------
Configuration files must have names ```web_conf.yaml``` and ```ipmi_local.conf```. If ipmi_exp_source_dir is specified, the role searches for ```web_conf.yaml``` and ```ipmi_local.conf``` in that directory and copy to target host in ```ipmi_exp_config_dir```. If the source directory is not specified, the role skips this step. In ipmi_local.conf user can describe what modules to use for metric collection.
ipmi_up{collector="<NAME>"} is 1 if the data for this collector could successfully be retrieved from the remote host, 0 otherwise. The following collectors are available and can be enabled or disabled in the config:
- ipmi: collects IPMI sensor data. If it fails, sensor metrics (see below) will not be available
- dcmi: collects DCMI data, currently only power consumption. If it fails, power consumption metrics (see below) will not be available
- bmc: collects BMC details. If it fails, BMC info metrics (see below) will not be available
- bmc-watchdog: collects status of the watchdog. If it fails, BMC watchdog metrics (see below) will not be available
- chassis: collects the current chassis power state (on/off). If it fails, the chassis power state metric (see below) will not be available
- sel: collects system event log (SEL) details. If it fails, SEL metrics (see below) will not be available
- sel-events: collects metrics for user-defined events in system event log (SEL). If it fails, SEL entries metrics (see below) will not be available
- sm-lan-mode: collects the "LAN mode" setting in the current BMC config. If it fails, the LAN mode metric (see below) will not be available
```yaml
ipmi_exp_version: 1.10.1
ipmi_exp_dir: "/etc/exporters"
ipmi_exp_config_dir: "/etc/exporters/config"
ipmi_exp_args: "" # --[no-]native-ipmi Use native IPMI implementation instead of FreeIPMI (EXPERIMENTAL)
# --[no-]web.systemd-socket Use systemd socket activation listeners instead of port listeners (Linux only).
ipmi_exp_log_level: "info" # Only log messages with the given severity or above. One of: [debug, info, warn, error]
ipmi_exp_log_format: "logfmt" # Output format of log messages. One of: [logfmt, json]
ipmi_exp_web_listen_address: "localhost:9290" # Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. Examples: `:9100` or `[::1]:9100` for http, vsock://:9100` for vsock
ipmi_exp_source_dir: ipmi_local.conf # Path to configuration file. See: https://github.com/prometheus-community/ipmi_exporter/blob/master/docs/configuration.md
ipmi_exp_web_source_dir: web_conf.yaml # Path to configuration file that can enable TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md
```
Dependencies
------------
None
Example Playbook
----------------
```yaml
roles:
- role: genlab.ipmi_exporter
ipmi_exp_version: "1.10.1"
ipmi_exp_source_dir: "molecule/default/"
```
License
-------
BSD
Author Information
------------------
corvus-migratorius@proton.me

View File

@@ -0,0 +1,8 @@
---
ipmi_exporter_version: 1.10.1
ipmi_exporter_dir: "/etc/exporters"
ipmi_exporter_config_dir: "/etc/exporters/config"
ipmi_exporter_log_level: "info"
ipmi_exporter_log_format: "logfmt"
ipmi_exporter_web_listen_address: "localhost:9290"
ipmi_exporter_args: ""

View File

@@ -0,0 +1,7 @@
---
- name: "(Re)start and enable ipmi_exporter"
ansible.builtin.systemd_service:
name: ipmi_exporter.service
state: restarted
enabled: true
daemon_reload: true

View File

@@ -0,0 +1,17 @@
---
galaxy_info:
role_name: "ipmi_exporter"
namespace: genlab
author: "Alexander Gorelyshev"
company: "Genlab, LLC"
description: ""
license: "MIT"
min_ansible_version: "2.1"
platforms:
- name: "Ubuntu"
versions: ["focal", "jammy", "noble"]
galaxy_tags: []
dependencies: []

View File

@@ -0,0 +1,6 @@
---
- name: Converge
hosts: all
roles:
- role: genlab.common.ipmi_exporter
ipmi_exp_source_dir: "molecule/default/"

View File

@@ -0,0 +1,12 @@
modules:
default:
# Available collectors are bmc, bmc-watchdog, ipmi, chassis, dcmi, sel, sel-events and sm-lan-mode
collectors:
- bmc
- bmc-watchdog
- ipmi
- dcmi
- chassis
- sel
- sel-events
- sm-lan-mode

View File

@@ -0,0 +1,27 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@@ -0,0 +1,37 @@
---
- name: Verify
hosts: all
gather_facts: false
any_errors_fatal: true
tasks:
- name: "Include default vars"
ansible.builtin.include_vars:
dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/"
extensions: ['yml']
- name: "Check if ipmi_exporter is installed"
changed_when: false
ansible.builtin.command: "ipmi_exporter --version"
register: ipmi_exporter_installed_version
- name: "Check ipmi_exporter version"
ansible.builtin.assert:
that: "ipmi_exporter_installed_version.stdout is regex('{{ ipmi_exporter_version }}')"
success_msg: "ipmi_exporter version {{ ipmi_exporter_version }} is installed and working"
fail_msg: "ipmi_exporter version {{ ipmi_exporter_version }} is not installed or not working correctly"
# kics-scan ignore-block
- name: Check if /metrics endpoint is reachable
ansible.builtin.uri:
url: "http://{{ ipmi_exporter_web_listen_address }}/metrics"
return_content: true
status_code: 200
timeout: 60
register: ipmi_exporter_metrics_check
- name: "Fail if /metrics doesn't contain ipmi_exporter_build_info line"
ansible.builtin.assert:
that: "'ipmi_exporter_build_info' in ipmi_exporter_metrics_check.content"
fail_msg: "ipmi_exporter /metrics endpoint doesn't contain ipmi_exporter_build_info line!"
success_msg: "ipmi_exporter /metrics endpoint contains ipmi_exporter_build_info line!"

View File

@@ -0,0 +1,20 @@
---
- name: "Upload ipmi_exporter local configure file"
notify: "(Re)start and enable ipmi_exporter"
when: ipmi_exp_source_dir is defined and ipmi_exp_source_dir | length > 0
ansible.builtin.template:
src: "{{ ipmi_exp_source_dir }}/ipmi_local.conf"
dest: "{{ ipmi_exporter_config_dir }}/ipmi_local.conf"
owner: root
group: root
mode: '0640'
- name: "Upload ipmi_exporter web configure file"
notify: "(Re)start and enable ipmi_exporter"
when: ipmi_exp_web_source_dir is defined and ipmi_exp_web_source_dir | length > 0
ansible.builtin.template:
src: "{{ ipmi_exp_web_source_dir }}/web_conf.yaml"
dest: "{{ ipmi_exporter_config_dir }}/web_conf.yaml"
owner: root
group: root
mode: '0640'

View File

@@ -0,0 +1,51 @@
---
- name: "Install ipmi_exporter from binary"
block:
- name: "Check ipmi_exporter version"
changed_when: false
ansible.builtin.command:
cmd: "ipmi_exporter --version"
register: ipmi_exporter_ver
- name: "Assert version correctness"
ansible.builtin.assert:
that: "ipmi_exporter_ver.stdout is regex('{{ ipmi_exporter_version }}')"
success_msg: "ipmi_exporter version {{ ipmi_exporter_version }} is installed and working"
fail_msg: "ipmi_exporter version {{ ipmi_exporter_version }} is not installed or not working correctly"
rescue:
- name: "Create ipmi_exporter directories"
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
with_items:
- "{{ ipmi_exporter_dir }}"
- "{{ ipmi_exporter_config_dir }}"
- name: "Unarchive ipmi_exporter tar file"
notify: "(Re)start and enable ipmi_exporter"
ansible.builtin.unarchive:
src: "https://github.com/prometheus-community/ipmi_exporter/releases/\
download/v{{ ipmi_exporter_version }}/ipmi_exporter-{{ ipmi_exporter_version }}.linux-amd64.tar.gz"
dest: "{{ ipmi_exporter_dir }}"
remote_src: true
- name: "Move ipmi_exporter binary"
ansible.builtin.copy:
src: "{{ ipmi_exporter_dir }}/ipmi_exporter-{{ ipmi_exporter_version }}.linux-amd64/ipmi_exporter"
dest: "/usr/local/bin/ipmi_exporter"
mode: "0755"
owner: root
group: root
remote_src: true
- name: Create systemd service file
ansible.builtin.template:
src: ipmi_exporter.service.j2
dest: /etc/systemd/system/ipmi_exporter.service
owner: root
group: root
mode: '0644'

View File

@@ -0,0 +1,7 @@
---
- name: "Include installation tasks"
ansible.builtin.include_tasks: "install.yml"
- name: "Including configuration tasks"
ansible.builtin.include_tasks: "configure.yml"

View File

@@ -0,0 +1,38 @@
[Unit]
Description=IPMI exporter
Documentation=https://github.com/prometheus-community/ipmi_exporter
After=network.target
StartLimitIntervalSec=120
StartLimitBurst=5
[Service]
Type=simple
ExecStart=/usr/local/bin/ipmi_exporter \
--web.listen-address={{ ipmi_exporter_web_listen_address }} \
--log.level={{ ipmi_exporter_log_level }} \
--log.format={{ ipmi_exporter_log_format }} \
{% if ipmi_exporter_args is defined and ipmi_exporter_args | length > 0 %}
{{ ipmi_exporter_args }} \
{% endif %}
{% if ipmi_exporter_source_dir is defined and ipmi_exporter_source_dir | length > 0 %}
--config.file={{ ipmi_exporter_config_dir }}/ipmi_local.conf \
{% endif %}
{% if ipmi_exporter_web_source_dir is defined and ipmi_exporter_web_source_dir | length > 0 %}
--web.config.file={{ ipmi_exporter_config_dir }}/web_conf.yaml
{% endif %}
SyslogIdentifier=ipmi_exporter
Restart=on-failure
RestartSec=5
ProtectHome=yes
NoNewPrivileges=yes
ProtectSystem=strict
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=yes
PrivateTmp=true
ProtectSystem=full
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1 @@
---

2
roles/karma/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode
.idea

53
roles/karma/README.md Normal file
View File

@@ -0,0 +1,53 @@
genlab.karma
=========
This is the ansible role to install and configure Karma - alert dashboard for Prometheus Alertmanager (https://github.com/prymitive/karma)
------------
⚠️ Do not forget to update:
- `meta/main.yml`
- Conda/Mamba manifests
- this README =) including *the name at the top* and *maintainers*.
Requirements
------------
None
Role Variables
--------------
```yaml
karma_version: "0.121" - karma version
karma_user: karma - name of karma system user
karma_group: karma - name of karma system group
karma_dir: /etc/karma - path where to unpack karma
karma_config_dir: /etc/karma/conf - where to copy configuration file
config_source_dir: source - path to source dir with karma config on localhost
```
Dependencies
------------
None
Example Playbook
----------------
```yaml
roles:
- role: genlab.karma
karma_version: "0.121"
config_source_dir: "karma/"
karma_dir: "/etc/karma"
karma_config_dir: "/etc/karma/config"
```
License
-------
BSD
Author Information
------------------
corvus-migratorius@proton.me

View File

@@ -0,0 +1,7 @@
---
# Default variables for Karma role
karma_version: "0.121"
karma_user: karma
karma_group: karma
karma_dir: /etc/karma
karma_config_dir: /etc/karma/conf

View File

@@ -0,0 +1,7 @@
---
- name: "(Re)start and enable karma"
ansible.builtin.systemd_service:
name: karma.service
state: restarted
enabled: true
daemon_reload: true

17
roles/karma/meta/main.yml Normal file
View File

@@ -0,0 +1,17 @@
---
galaxy_info:
role_name: "karma"
namespace: genlab
author: "Alexander Gorelyshev"
company: "Genlab, LLC"
description: ""
license: "MIT"
min_ansible_version: "2.1"
platforms:
- name: "Ubuntu"
versions: ["focal", "jammy", "noble"]
galaxy_tags: [monitoring]
dependencies: []

View File

@@ -0,0 +1,9 @@
---
- name: Converge
hosts: all
gather_facts: true
become: true
roles:
- role: genlab.common.karma
karma_version: "0.121"
config_source_dir: molecule/default/

View File

@@ -0,0 +1,17 @@
alertmanager:
interval: 1m
servers:
- name: production
uri: http://localhost:9093
timeout: 20s
proxy: false
readonly: true
listen:
address: "0.0.0.0"
port: 8080
prefix: /
tls:
cert: ""
key: ""
cors:
allowedOrigins: []

View File

@@ -0,0 +1,27 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
pre_build_image: true
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
provisioner:
name: ansible
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint .

View File

@@ -0,0 +1,35 @@
---
- name: Verify
hosts: all
gather_facts: false
any_errors_fatal: true
vars:
karma_version: "0.121"
tasks:
- name: "Check if Karma is installed"
changed_when: false
ansible.builtin.command: "karma --version"
register: karma_installed_version
- name: "Check Karma version"
ansible.builtin.assert:
that: "karma_installed_version.stdout is regex('{{ karma_version }}')"
success_msg: "Karma version {{ karma_version }} is installed and working"
fail_msg: "Karma version {{ karma_version }} is not installed or not working correctly"
# kics-scan ignore-block
- name: "Check if Karma is reachable"
ansible.builtin.uri:
url: "http://localhost:8080/health"
return_content: true
status_code: 200
method: GET
body_format: json
register: karma_health
- name: "Assert Karma health status"
ansible.builtin.assert:
that: "karma_health.content.strip() == 'Pong'"
success_msg: "Karma is healthy"
fail_msg: "Karma is not healthy"

View File

@@ -0,0 +1,10 @@
---
- name: Create karma configuration file
notify: "(Re)start and enable karma"
ansible.builtin.template:
src: "{{ config_source_dir }}/karma.conf"
dest: "{{ karma_config_dir }}/karma.conf"
owner: "{{ karma_user }}"
group: "{{ karma_group }}"
mode: '0640'

View File

@@ -0,0 +1,66 @@
---
- name: "Create Karma system group"
ansible.builtin.group:
name: "{{ karma_user }}"
system: true
state: present
- name: "Create karma system user"
ansible.builtin.user:
name: "{{ karma_user }}"
group: "{{ karma_group }}"
system: true
shell: "/sbin/nologin"
create_home: false
state: present
- name: "Install karma from binary"
block:
- name: "Check karma version"
changed_when: false
ansible.builtin.command:
cmd: "{{ karma_dir }}/karma-linux-amd64 --version"
register: karma_ver
- name: "Assert version correctness"
ansible.builtin.assert:
that: "karma_ver.stdout is regex('{{ karma_version }}')"
success_msg: "karma version {{ karma_version }} is installed and working"
fail_msg: "karma version {{ karma_version }} is not installed or not working correctly"
rescue:
- name: "Create karma directories"
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ karma_user }}"
group: "{{ karma_group }}"
mode: "0755"
with_items:
- "{{ karma_dir }}"
- "{{ karma_config_dir }}"
- name: "Unarchive karma tar file"
notify: "(Re)start and enable karma"
ansible.builtin.unarchive:
src: "https://github.com/prymitive/karma/releases/download/v{{ karma_version }}/karma-linux-amd64.tar.gz"
dest: "{{ karma_dir }}"
remote_src: true
- name: "Move karma binary"
ansible.builtin.copy:
src: "{{ karma_dir }}/karma-linux-amd64"
dest: "/usr/local/bin/karma"
mode: "0755"
owner: "{{ karma_user }}"
group: "{{ karma_group }}"
remote_src: true
- name: Create systemd service file
ansible.builtin.template:
src: karma.service.j2
dest: /etc/systemd/system/karma.service
owner: "{{ karma_user }}"
group: "{{ karma_group }}"
mode: '0644'

View File

@@ -0,0 +1,9 @@
---
- name: "Run installation tasks"
ansible.builtin.include_tasks: install.yml
- name: "Run configuration tasks"
ansible.builtin.include_tasks: configuration.yml
- name: "Flush handlers"
ansible.builtin.meta: "flush_handlers"

View File

@@ -0,0 +1,26 @@
[Unit]
Description=Karma - Alertmanager dashboard
After=network.target
Documentation=https://github.com/prymitive/karma
[Service]
Type=simple
User={{ karma_user }}
Group={{ karma_group }}
ExecStart=karma --config.file={{ karma_config_dir }}/karma.conf
Restart=always
RestartSec=5
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=karma
ProtectSystem=strict
NoNewPrivileges=true
PrivateTmp=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectClock=yes
RestrictSUIDSGID=true
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1 @@
---

43
roles/loki/README.md Normal file
View File

@@ -0,0 +1,43 @@
loki
=========
Installs Loki as a `systemd` service.
Requirements
------------
Role Variables
--------------
```yaml
loki_version: Loki version to be deployed
loki_storage_path: where to put Loki's files, including log data and positions (default: /data/loki)
loki_port: Loki will listen on this port (default: 3000)
```
Dependencies
------------
No
Example Playbook
----------------
```yaml
roles:
- role: loki
loki_version: 2.7.3
```
License
-------
MIT
Author Information
------------------
Alexander Gorelyshev (corvus-migratorius@proton.me) and Danila Danilkin
Genlab LLC

View File

@@ -0,0 +1,4 @@
---
loki_version: 3.4.2
loki_storage_path: /data/loki
loki_port: 3100

View File

@@ -0,0 +1,10 @@
---
- name: "Restart the Loki daemon"
ansible.builtin.systemd:
name: loki
state: restarted
- name: "Reload the Loki daemon configuration"
ansible.builtin.systemd:
daemon_reload: true

Some files were not shown because too many files have changed in this diff Show More