Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa83361a8e | |||
| c2149834d3 | |||
| 5db2481d76 | |||
| eefd846f7c | |||
| 63e1012ff3 | |||
| 8317e78bef | |||
| f883424e26 | |||
| eab95a5b54 | |||
| 96211f2eb5 | |||
| 4217cb39d1 | |||
| 7972cfd5ad | |||
| 45ce68ea3d | |||
|
|
02ac780491 | ||
|
|
425fd79204 | ||
| f3923c6330 | |||
|
|
b55920b478 | ||
|
|
2f6fea62b3 | ||
| a59c9727e0 | |||
|
|
b12f4bd203 | ||
|
|
413c5c3679 | ||
| a1d70fb4ab | |||
|
|
5953ff411a | ||
|
|
f0a398de6a | ||
|
|
e229e5ed45 | ||
|
|
cd249e15fc | ||
|
|
fd638a276f | ||
|
|
834cf30fdd | ||
|
|
a1efbd6715 | ||
|
|
3bd4b55669 | ||
|
|
34736f529f | ||
|
|
46c93d1561 | ||
|
|
31fa60bd53 | ||
|
|
2df266dac9 | ||
|
|
f5ebc7ba33 | ||
|
|
e4746d356a | ||
|
|
888ad62fe1 | ||
|
|
017ef494b6 | ||
|
|
38f597d115 | ||
|
|
a44a82ab57 | ||
|
|
7973d1ec96 | ||
|
|
e77d14928b | ||
|
|
7a1749a738 | ||
|
|
7200fcb282 | ||
|
|
5d0f3cb5f4 | ||
|
|
2781df9d27 | ||
|
|
4e9793519d | ||
|
|
183e479c75 | ||
|
|
88b5407e15 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
||||
Gemfile.lock
|
||||
FileList
|
||||
.scannerwork
|
||||
.vscode
|
||||
.puppet-lint.rc
|
||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"endscript",
|
||||
"maillog",
|
||||
"missingok",
|
||||
"postrotate",
|
||||
"sharedscripts",
|
||||
"syslogs"
|
||||
]
|
||||
}
|
||||
29
Jenkinsfile
vendored
29
Jenkinsfile
vendored
@@ -69,7 +69,7 @@ pipeline {
|
||||
withCredentials([string(credentialsId: 'sonar-token', variable: 'SONAR_TOKEN')]) {
|
||||
sh '''
|
||||
/opt/sonar-scanner/bin/sonar-scanner \
|
||||
-Dsonar.projectKey=alloy_cd \
|
||||
-Dsonar.projectKey=confdroid_alloy \
|
||||
-Dsonar.sources=. \
|
||||
-Dsonar.host.url=https://sonarqube.confdroid.com \
|
||||
-Dsonar.token=$SONAR_TOKEN
|
||||
@@ -90,11 +90,38 @@ pipeline {
|
||||
sh '''
|
||||
git config user.name "Jenkins Server"
|
||||
git config user.email jenkins@confdroid.com
|
||||
git rm -r --cached .vscode || echo "No .vscode to remove from git"
|
||||
git add -A && git commit -am "Recommit for updates in build $BUILD_NUMBER" || echo "No changes to commit"
|
||||
git push origin HEAD:master
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Mirror to Gitea') {
|
||||
steps {
|
||||
sshagent(['edd05eb6-26b5-4c7b-a5cc-ea2ab899f4fa']) {
|
||||
withCredentials([usernamePassword(
|
||||
credentialsId: 'Jenkins-gitea',
|
||||
usernameVariable: 'GITEA_USER',
|
||||
passwordVariable: 'GITEA_TOKEN')]) {
|
||||
script {
|
||||
// Checkout from GitLab (already done implicitly)
|
||||
sh '''
|
||||
git checkout master
|
||||
git pull origin master
|
||||
git branch -D development
|
||||
git branch -D jenkins-build-$BUILD_NUMBER
|
||||
git rm -f Jenkinsfile
|
||||
git rm -r --cached .vscode || echo "No .vscode to remove from git"
|
||||
git commit --amend --no-edit --allow-empty
|
||||
git remote add master https://sourcecode.confdroid.com/confdroid/confdroid_alloy.git
|
||||
git -c credential.helper="!f() { echo username=${GITEA_USER}; echo password=${GITEA_TOKEN}; }; f" \
|
||||
push master --mirror
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
README.md
61
README.md
@@ -1,14 +1,32 @@
|
||||
# Readme
|
||||
|
||||
[](https://jenkins.confdroid.com/job/alloy_cd/)
|
||||
[](https://jenkins.confdroid.com/job/confdroid_alloy/)
|
||||
[](https://sonarqube.confdroid.com/dashboard?id=confdroid_alloy)
|
||||
[](https://deepwiki.com/grizzlycoda/puppet_collection)
|
||||
|
||||
[[_TOC_]]
|
||||
- [Readme](#readme)
|
||||
- [Synopsis](#synopsis)
|
||||
- [WARNING](#warning)
|
||||
- [Features](#features)
|
||||
- [Repo Documentation](#repo-documentation)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Deployment](#deployment)
|
||||
- [SELINUX](#selinux)
|
||||
- [Known Problems](#known-problems)
|
||||
- [Support](#support)
|
||||
- [Tests](#tests)
|
||||
- [Contact Us](#contact-us)
|
||||
- [Disclaimer](#disclaimer)
|
||||
|
||||
## Synopsis
|
||||
|
||||
Grafana Alloy is a log management agent built by Grafana labs to scrape and forward logs to Loki, a log management system also built by Grafana labs. Then you can use Grafana itself to visualize those logs and built an alerting system.
|
||||
Grafana Alloy is a log management agent built by Grafana labs to scrape and forward logs to Loki, a log management system also built by Grafana labs.
|
||||
|
||||
`alloy_cd` is a Puppet module to automate installation and configuration of alloy.
|
||||
It also can scrape and forward metrics to Prometheus.
|
||||
|
||||
Then you can use Grafana itself to visualize those logs and metrics and built an alerting system.
|
||||
|
||||
`confdroid_alloy` is a Puppet module to automate installation and configuration of alloy.
|
||||
|
||||
## WARNING
|
||||
|
||||
@@ -18,13 +36,15 @@ Grafana Alloy is a log management agent built by Grafana labs to scrape and forw
|
||||
|
||||
INSTALLATION
|
||||
|
||||
* installs alloy binaries
|
||||
- installs alloy binaries
|
||||
|
||||
CONFIGURATION
|
||||
|
||||
* manage directories and files including templates
|
||||
* manages the main alloy jenkins for loki and prometheus
|
||||
* manage service
|
||||
- manage directories and files including templates
|
||||
- manages the main alloy pipeline for loki and prometheus
|
||||
- manages log rotation for rsyslog to allow adm membership
|
||||
- manage service
|
||||
- optional: include nagios target for the alloy service (set `ay_manage_nagios`to `true`)
|
||||
|
||||
## Repo Documentation
|
||||
|
||||
@@ -34,19 +54,21 @@ See the full Puppet documentation including parameters in `docs/index.html`
|
||||
|
||||
All dependencies must be included in the catalogue.
|
||||
|
||||
- [confdroid_nagios](https://sourcecode.confdroid.com/confdroid/confdroid_nagios) if nagios monitoring is desired
|
||||
|
||||
## Deployment
|
||||
|
||||
* native Puppet deployment
|
||||
- native Puppet deployment
|
||||
|
||||
via site.pp or nodes.pp
|
||||
|
||||
```bash
|
||||
node 'example.example.net' {
|
||||
include cd_kubernetes
|
||||
include confdroid_alloy
|
||||
}
|
||||
```
|
||||
|
||||
* through Foreman:
|
||||
- through Foreman:
|
||||
|
||||
In order to apply parameters through Foreman, the params.pp must be added to the host or host group in question.
|
||||
|
||||
@@ -60,19 +82,22 @@ All files and directories are configured with correct selinux context. If selinu
|
||||
|
||||
## Support
|
||||
|
||||
* OS: Rocky 9
|
||||
* Puppet 6,8
|
||||
- OS: Rocky 9
|
||||
- Puppet 8
|
||||
|
||||
## Tests
|
||||
|
||||
* Puppet Lint
|
||||
* Puppet Parser
|
||||
* ERB Template Parser
|
||||
* Sonar Quality Gate
|
||||
- Puppet Lint
|
||||
- excluded tests:
|
||||
- `--no-variable_scope-check`: not applicable as we are inheriting parameters from params class. the lint check does not distinguish between facts and inherited parameters.
|
||||
- Puppet Parser
|
||||
- ERB Template Parser
|
||||
- Sonar Quality Gate
|
||||
|
||||
## Contact Us
|
||||
|
||||
[contact Us](https://confdroid.com/contact/)
|
||||
- [contact Us](https://confdroid.com/contact/)
|
||||
- [Feedback Portal](https://feedback.confdroid.com)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## alloy_cd::init.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
# @summary Class initializes the alloy_cd module.
|
||||
## confdroid_alloy::init.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @summary Class initializes the confdroid_alloy module.
|
||||
##############################################################################
|
||||
class alloy_cd {
|
||||
include alloy_cd::params
|
||||
class confdroid_alloy {
|
||||
include confdroid_alloy::params
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#### alloy_cd::main::config.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
# @summary Class manages module logic for the alloy_cd module
|
||||
#### confdroid_alloy::main::config.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @summary Class manages module logic for the confdroid_alloy module
|
||||
###############################################################################
|
||||
class alloy_cd::main::config (
|
||||
class confdroid_alloy::main::config (
|
||||
|
||||
) inherits alloy_cd::params {
|
||||
include alloy_cd::main::service
|
||||
) inherits confdroid_alloy::params {
|
||||
include confdroid_alloy::main::service
|
||||
|
||||
if $ay_manage_nagios == true {
|
||||
include confdroid_alloy::monitoring::target
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
## alloy_cd::main::dirs.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
# @summary Class manages required directories for the alloy_cd module
|
||||
## confdroid_alloy::main::dirs.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @summary Class manages required directories for the confdroid_alloy module
|
||||
###############################################################################
|
||||
class alloy_cd::main::dirs (
|
||||
class confdroid_alloy::main::dirs (
|
||||
|
||||
) inherits alloy_cd::params {
|
||||
require alloy_cd::main::install
|
||||
) inherits confdroid_alloy::params {
|
||||
require confdroid_alloy::main::install
|
||||
|
||||
# main config dir
|
||||
file { $ay_main_dir:
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
## alloy_cd::main::files.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
# @summary Class manages required config files for the alloy_cd module
|
||||
## confdroid_alloy::main::files.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @summary Class manages required config files for the confdroid_alloy module
|
||||
###############################################################################
|
||||
class alloy_cd::main::files (
|
||||
class confdroid_alloy::main::files (
|
||||
|
||||
) inherits alloy_cd::params {
|
||||
require alloy_cd::main::dirs
|
||||
) inherits confdroid_alloy::params {
|
||||
require confdroid_alloy::main::dirs
|
||||
|
||||
# Ensure log_targets is always an array
|
||||
$log_targets_array = Array($alloy_cd::params::ay_log_targets, true)
|
||||
$log_targets_array = Array($confdroid_alloy::params::ay_log_targets, true)
|
||||
|
||||
file { $ay_main_file:
|
||||
ensure => file,
|
||||
@@ -20,7 +20,7 @@ class alloy_cd::main::files (
|
||||
selrole => object_r,
|
||||
seltype => etc_t,
|
||||
seluser => system_u,
|
||||
content => template('alloy_cd/config.alloy.erb'),
|
||||
content => template('confdroid_alloy/config.alloy.erb'),
|
||||
notify => Service[$ay_service],
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class alloy_cd::main::files (
|
||||
selrole => object_r,
|
||||
seltype => etc_t,
|
||||
seluser => system_u,
|
||||
content => template('alloy_cd/logrotate.rsyslog.erb'),
|
||||
content => template('confdroid_alloy/logrotate.rsyslog.erb'),
|
||||
}
|
||||
|
||||
# manage log file group permissions
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
## alloy_cd::main::install.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
# @summary Class installs packages for the alloy_cd module
|
||||
## confdroid_alloy::main::install.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @summary Class installs packages for the confdroid_alloy module
|
||||
###############################################################################
|
||||
class alloy_cd::main::install (
|
||||
class confdroid_alloy::main::install (
|
||||
|
||||
) inherits alloy_cd::params {
|
||||
require alloy_cd::main::yumrepo
|
||||
) inherits confdroid_alloy::params {
|
||||
require confdroid_alloy::main::yumrepo
|
||||
|
||||
package { $reqpackages:
|
||||
ensure => $pkg_ensure,
|
||||
package { $ay_reqpackages:
|
||||
ensure => $ay_pkg_ensure,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
## alloy_cd::main::service.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
## confdroid_alloy::main::service.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @summary Class manages the alloy service
|
||||
###############################################################################
|
||||
class alloy_cd::main::service (
|
||||
class confdroid_alloy::main::service (
|
||||
|
||||
) inherits alloy_cd::params {
|
||||
require alloy_cd::main::files
|
||||
) inherits confdroid_alloy::params {
|
||||
require confdroid_alloy::main::files
|
||||
|
||||
service { $ay_service:
|
||||
ensure => running,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
## alloy_cd::main::yumrepo.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
# @summary class install yum repos for the alloy_cd module
|
||||
## confdroid_alloy::main::yumrepo.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @summary class install yum repos for the confdroid_alloy module
|
||||
##############################################################################
|
||||
class alloy_cd::main::yumrepo (
|
||||
|
||||
) inherits alloy_cd::params {
|
||||
class confdroid_alloy::main::yumrepo (
|
||||
|
||||
) inherits confdroid_alloy::params {
|
||||
# test
|
||||
yumrepo { 'grafana':
|
||||
ensure => 'present',
|
||||
|
||||
23
manifests/monitoring/target.pp
Normal file
23
manifests/monitoring/target.pp
Normal file
@@ -0,0 +1,23 @@
|
||||
## confdroid_alloy::monitoring::target.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@puppetsoft.com)
|
||||
# @summary class manages exports for nagios monitoring
|
||||
##############################################################################
|
||||
class confdroid_alloy::monitoring::target (
|
||||
|
||||
) inherits confdroid_alloy::params {
|
||||
if $ay_manage_nagios == true {
|
||||
@@nagios_service { "check_alloy_${fqdn}":
|
||||
check_command => "check_nrpe!check_alloy!${ay_procs_allowed}!alloy",
|
||||
use => 'generic-service',
|
||||
host_name => $fqdn,
|
||||
notification_period => '24x7',
|
||||
service_description => "${fqdn}_check_alloy",
|
||||
target => $ay_target_service,
|
||||
owner => 'nagios',
|
||||
group => 'nagios',
|
||||
mode => '0640',
|
||||
contacts => $ay_target_contacts,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
## alloy_cd::params.pp
|
||||
# Module name: alloy_cd
|
||||
# Author: Arne Teuke (arne_teuke@confdroid.com)
|
||||
# @param [String] reqpackages which packages to install
|
||||
# @param [String] pkg_ensure 'latest' or 'present'
|
||||
## confdroid_alloy::params.pp
|
||||
# Module name: confdroid_alloy
|
||||
# Author: 12ww1160 (12ww1160@confdroid.com)
|
||||
# @param [String] ay_reqpackages which packages to install
|
||||
# @param [String] ay_pkg_ensure 'latest' or 'present'
|
||||
# @param [String] ay_loki_url the url where alloy will send logs to.
|
||||
# @param [String] ay_loki_username the username to logon to loki
|
||||
# @param [String] ay_loki_userpass the password to logon to loki
|
||||
@@ -10,12 +10,23 @@
|
||||
# @param [Boolean] ay_manage_prom whether to manage metric forwarding
|
||||
# @param [String] ay_prom_url the url where alloy will send metrics
|
||||
# @param [Array] ay_log_targets the logging targets
|
||||
# @summary Class contains all parameters for the alloy_cd module.
|
||||
# @param [Boolean] ay_prom_honor_timestamps whether to honor
|
||||
# timestamps in prometheus metrics
|
||||
# @param [Boolean] ay_prom_track_timestamps_staleness whether to track timestamps
|
||||
# staleness in prometheus metrics
|
||||
# @param [String] ay_prom_max_shards the maximum number of shards to use for prometheus metrics
|
||||
# @param [String] ay_prom_max_samples_per_send the maximum number of samples to send
|
||||
# @param [String] ay_prom_batch_send_deadline the maximum time to wait before sending
|
||||
# @param [Boolean] ay_manage_nagios whether to manage nagios service exports
|
||||
# @param [String] ay_target_service the target file for nagios service exports
|
||||
# @param [Array] ay_target_contacts the contacts for nagios service exports
|
||||
# @param [String] ay_procs_allowed the allowed number of processes for nagios service exports
|
||||
# @summary Class contains all parameters for the confdroid_alloy module.
|
||||
##############################################################################
|
||||
class alloy_cd::params (
|
||||
class confdroid_alloy::params (
|
||||
|
||||
String $reqpackages = 'alloy',
|
||||
String $pkg_ensure = 'latest',
|
||||
String $ay_reqpackages = 'alloy',
|
||||
String $ay_pkg_ensure = 'latest',
|
||||
|
||||
# loki
|
||||
Boolean $ay_manage_loki = true,
|
||||
@@ -27,8 +38,22 @@ class alloy_cd::params (
|
||||
# prometheus
|
||||
Boolean $ay_manage_prom = true,
|
||||
String $ay_prom_url = 'https://prometheus.example.net/api/v1/write',
|
||||
Boolean $ay_prom_honor_timestamps = true,
|
||||
Boolean $ay_prom_track_timestamps_staleness = true,
|
||||
String $ay_prom_max_shards = '10',
|
||||
String $ay_prom_max_samples_per_send = '2000',
|
||||
String $ay_prom_batch_send_deadline = '5s',
|
||||
|
||||
# nagios
|
||||
Boolean $ay_manage_nagios = false,
|
||||
String $ay_target_service = '/etc/nagios/conf.d/alloy_service.cfg',
|
||||
Array $ay_target_contacts = ['nagiosadmin'],
|
||||
String $ay_procs_allowed = '1:1'
|
||||
|
||||
) {
|
||||
# defaults
|
||||
$fqdn = $facts['networking']['fqdn']
|
||||
|
||||
# service
|
||||
$ay_service = 'alloy'
|
||||
|
||||
@@ -39,5 +64,5 @@ class alloy_cd::params (
|
||||
$ay_main_file = "${ay_main_dir}/config.alloy"
|
||||
|
||||
# includes must be last
|
||||
include alloy_cd::main::config
|
||||
include confdroid_alloy::main::config
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<% if @ay_manage_loki == true -%>
|
||||
|
||||
logging{
|
||||
level = "debug"
|
||||
level = "info"
|
||||
format = "logfmt"
|
||||
write_to = [loki.process.alloy.receiver]
|
||||
}
|
||||
@@ -53,25 +53,52 @@ loki.write "loki" {
|
||||
}
|
||||
<% end -%>
|
||||
<% if @ay_manage_prom == true -%>
|
||||
// Metrics collection
|
||||
prometheus.exporter.unix "system_metrics" {
|
||||
include_exporter_metrics = true
|
||||
disable_collectors = ["mdadm"]
|
||||
// 1. Exporter (or keep your existing node_exporter binary)
|
||||
prometheus.exporter.unix "node" {
|
||||
// optional: disable unused collectors
|
||||
// disable_collectors = ["ipvs", "btrfs", ...]
|
||||
}
|
||||
|
||||
prometheus.scrape "scrape_system" {
|
||||
targets = prometheus.exporter.unix.system_metrics.targets
|
||||
// 2. Relabel the targets so instance = real hostname
|
||||
discovery.relabel "node_exporter" {
|
||||
targets = prometheus.exporter.unix.node.targets
|
||||
|
||||
rule {
|
||||
target_label = "instance"
|
||||
replacement = constants.hostname
|
||||
}
|
||||
|
||||
rule {
|
||||
target_label = "job"
|
||||
replacement = "integrations/node_exporter"
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Scrape the properly labeled targets
|
||||
prometheus.scrape "node_exporter" {
|
||||
targets = discovery.relabel.node_exporter.output
|
||||
forward_to = [prometheus.remote_write.prometheus.receiver]
|
||||
scrape_interval = "15s"
|
||||
honor_timestamps = <%= @ay_prom_honor_timestamps %>
|
||||
track_timestamps_staleness = <%= @ay_prom_track_timestamps_staleness %>
|
||||
}
|
||||
|
||||
prometheus.remote_write "prometheus" {
|
||||
external_labels = {
|
||||
// makes every series from this Alloy unmistakably unique
|
||||
alloy_instance = constants.hostname,
|
||||
}
|
||||
endpoint {
|
||||
url = "<%= @ay_prom_url %>"
|
||||
basic_auth {
|
||||
username = "<%= @ay_loki_username %>"
|
||||
password = "<%= @ay_loki_userpass %>"
|
||||
}
|
||||
queue_config {
|
||||
max_shards = <%= @ay_prom_max_shards %>
|
||||
max_samples_per_send = <%= @ay_prom_max_samples_per_send %>
|
||||
batch_send_deadline = "<%= @ay_prom_batch_send_deadline %>"
|
||||
}
|
||||
}
|
||||
}
|
||||
<% end %>
|
||||
Reference in New Issue
Block a user