[comment]: # ({f238de62-e03ef403})
# Создание плагина (руководство)

Это пошаговое руководство по созданию простого загружаемого плагина для Zabbix Agent 2.

Вы также можете использовать [репозиторий примеров](https://git.zabbix.com/projects/AP/repos/example/browse) в качестве шаблона или руководства для создания собственных плагинов.

[comment]: # ({/f238de62-e03ef403})

[comment]: # ({4aa59460-3b9b2b36})
### Что вы создадите

В этом руководстве показано, как создать новый загружаемый плагин **MyIP**.
Плагин будет реализовывать один ключ элемента, **myip**, который возвращает внешний IP-адрес хоста, на котором запущен Zabbix агент 2.

[comment]: # ({/4aa59460-3b9b2b36})

[comment]: # ({bf3f1fff-47915903})
### Шаг 1: Настройка

1. Плагин — это стандартный модуль Go.

Начните с инициализации файла `go.mod` в каталоге плагина для отслеживания зависимостей плагина:

```sh
cd path/to/plugins/myip # Перейдите в каталог вашего плагина
go mod init myip
```

2. Установите обязательную зависимость Zabbix Go SDK (`golang.zabbix.com/sdk`):

```sh
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH
```

Замените `$LATEST_COMMIT_HASH` на последний хеш коммита `HEAD` из `golang.zabbix.com/sdk` [репозитория](https://git.zabbix.com/projects/AP/repos/plugin-support/commits) в соответствующей ветке релиза.
Например:

```sh
go get golang.zabbix.com/sdk@af85407
```

Обратите внимание, что версионирование `golang.zabbix.com/sdk` в настоящее время не поддерживается, но это может измениться в будущем.

При необходимости дополнительные зависимости можно установить с помощью `go get`.

3. Создайте пустой файл `main.go` для исходного кода плагина:

```sh
touch main.go
```

Теперь начальная настройка завершена, и плагин готов к разработке.

[comment]: # ({/bf3f1fff-47915903})

[comment]: # ({f8ebbdf8-a76d6ea7})
### Шаг 2: Структура плагина

Модуль `golang.zabbix.com/sdk`, установленный на предыдущем шаге, предоставляет необходимую основу для разработки плагинов и обеспечивает единообразную структуру всех плагинов.

1. Настройте базовый поток выполнения.

Начните с определения основного потока выполнения плагина. Добавьте следующий код в `main.go`:

```go
package main

func main() {
	err := run()
	if err != nil {
		panic(err)
	}
}

func run() error {
	return nil
}
```

Это задает базовый поток выполнения для плагина.
Функция run позже будет содержать основную логику плагина.

2. Изучите интерфейсы плагина.

Плагин агента Zabbix 2 должен быть представлен структурой, которая реализует интерфейсы из пакета `golang.zabbix.com/sdk/plugin`:

- *Accessor* - определяет основные методы, которые должны реализовывать все плагины, например задание имени плагина и обработку тайм-аутов ключей элементов данных.
- Один или несколько из следующих функциональных интерфейсов плагина:
	- *Exporter* - выполняет опрос и возвращает значение (или значения), ничего или ошибку; часто используется вместе с интерфейсом *Collector*.
	- *Collector* - управляет периодическим сбором данных.
	- *Runner* - определяет процедуры запуска и завершения работы плагина.
	- *Watcher* - позволяет реализовать независимый опрос метрик, обходя внутренний планировщик агента; полезно для мониторинга на основе ловушек или событий.
	- *Configurator* - определяет, как плагин читает и применяет свои параметры конфигурации.

Вы можете реализовать эти интерфейсы самостоятельно или использовать стандартные, предоставляемые Zabbix Go SDK, при необходимости изменяя их.
В этом руководстве используются стандартные реализации.

3. Создайте структуру плагина.

Теперь импортируйте пакет *plugin* и создайте структуру `myIP`, которая включает структуру `plugin.Base`:

```go
import "golang.zabbix.com/sdk/plugin"

type myIP struct {
	plugin.Base
}
```

Структура myIP в настоящее время удовлетворяет интерфейсу Accessor.
Метод для реализации одного из функциональных интерфейсов плагина, `Exporter`, будет добавлен позже в руководстве.

[comment]: # ({/f8ebbdf8-a76d6ea7})

[comment]: # ({c3726cdb-4c521593})
### Шаг 3: Определите ключи элементов данных

Вашему плагину нужны ключи элементов данных, чтобы собирать данные и передавать их серверу Zabbix или прокси.

1. Импортируйте пакет *errs* для обработки ошибок:

```go
import "golang.zabbix.com/sdk/errs"
```

2. Зарегистрируйте ключи элементов данных с помощью функции `plugin.RegisterMetrics()` внутри функции `run()`:

```go
func run() error {
	p := &myIP{}

	// Register the `myip` item key.
	err := plugin.RegisterMetrics(
		p,
		"MyIP",                           // Plugin name
		"myip",                           // Item key name
		"Returns the host's IP address.", // Item key description
	)
	if err != nil {
		return errs.Wrap(err, "failed to register metrics")
	}

	return nil
}
```

Чтобы зарегистрировать несколько ключей элементов данных, повторите параметры *metric name* и *description* для каждой метрики. 
Например:

```go
plugin.RegisterMetrics(&impl, "Myip", "metric.one", "Metric one description.", "metric.two", "Metric two description.")
```

[comment]: # ({/c3726cdb-4c521593})

[comment]: # ({8227eb99-b7f436a7})
### Шаг 4: Настройка обработчика

Обработчик обеспечивает связь между агентом и плагином.

1. Импортируйте пакет *container*:

```go
import "golang.zabbix.com/sdk/plugin/container"
```

2. Внутри функции `run()` добавьте код для создания и настройки обработчика:

```go
func run() error {
	p := &myIP{}

	// Register the `myip` item key.
	err := plugin.RegisterMetrics(
		p,
		"MyIP",                           // Plugin name
		"myip",                           // Item key name
		"Returns the host's IP address.", // Item key description
	)
	if err != nil {
		return errs.Wrap(err, "failed to register metrics")
	}

	// Create a new handler.
	h, err := container.NewHandler("MyIP") // Plugin name
	if err != nil {
		return errs.Wrap(err, "failed to create new handler")
	}

	// Setup logging to forward logs from the plugin to the agent.
	// Available via p.Logger.Infof, p.Logger.Debugf, etc.
	p.Logger = h

	// Start plugin execution.
	// Blocks until a termination request is received from the agent.
	err = h.Execute()
	if err != nil {
		return errs.Wrap(err, "failed to execute plugin handler")
	}

	return nil
}
```

[comment]: # ({/8227eb99-b7f436a7})

[comment]: # ({d6f0f4ab-c7541684})
### Шаг 5: Реализация сбора данных

Сбор данных выполняется через интерфейс Exporter, который описывает метод `Export`:

```go
func Export(
  key string,             // Ключ элемента данных для сбора.
  params []string,        // Аргументы, передаваемые ключу элемента данных (`myip[arg1, arg2]`).
  context ContextProvider // Метаданные о сборе данных по ключу элемента данных.
) (any, error)
```

1. Импортируйте необходимые пакеты для HTTP-запросов и чтения ответа:

```
import (
	"io"
	"net/http"
)
```

2. Реализуйте метод `Export` для структуры `myIP`:

```go
func (p *myIP) Export(
	key string, params []string, context plugin.ContextProvider,
) (any, error) {
	// Плагин может использовать разную логику сбора данных в зависимости от параметра `key`.
    // Эта реализация только проверяет, что переданный `key` поддерживается.
	if key != "myip" {
		return nil, errs.Errorf("unknown item key %q", key)
	}

	// Журнал будет перенаправлен в журнал агента 2.
	p.Infof(
		"received request to handle %q key with %d parameters",
		key,
		len(params),
	)

	// Соберите данные и верните их.

	resp, err := http.Get("https://api.ipify.org")
	if err != nil {
		return nil, errs.Wrap(err, "failed to get IP address")
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, errs.Wrap(err, "failed to read response body")
	}

	return string(body), nil
}
```

[comment]: # ({/d6f0f4ab-c7541684})

[comment]: # ({c7bcafc2-f9325698})
### Шаг 6: Сборка и настройка плагина

1. Чтобы собрать плагин, выполните:

```bash
go mod tidy
go build
```

Это должно создать исполняемый файл `myip` в текущем каталоге.

2. Настройте агент Zabbix 2 для использования плагина:

```sh
echo "Plugins.MyIP.System.Path=$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE" > /etc/zabbix_agent2.d/plugins.d/myip.conf
```

Замените `$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE` на путь к `myip`, созданному на шаге 5.

Имя плагина в имени параметра конфигурации (_MyIP_ в этом руководстве) должно совпадать с именем плагина, определенным в функции _plugin.RegisterMetrics()_.

3. Чтобы протестировать плагин и его элемент данных `myip`, выполните:

```sh
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip
```

Вывод должен содержать внешний IP-адрес вашего узла сети и выглядеть примерно так:

```
myip                                          [s|192.0.2.1]
```

На этом вы создали простой загружаемый плагин для агента Zabbix 2.
Поздравляем!

[comment]: # ({/c7bcafc2-f9325698})

[comment]: # ({6aef1ccf-38f17bf9})
### Полный исходный код

```go
package main

import (
	"io"
	"net/http"

	"golang.zabbix.com/sdk/errs"
	"golang.zabbix.com/sdk/plugin"
	"golang.zabbix.com/sdk/plugin/container"
)

var _ plugin.Exporter = (*myIP)(nil)

type myIP struct {
	plugin.Base
}

func main() {
	err := run()
	if err != nil {
		panic(err)
	}
}

func run() error {
	p := &myIP{}

	// Зарегистрируйте ключ элемента данных `myip`.
	err := plugin.RegisterMetrics(
		p,
		"MyIP",                           // Имя плагина
		"myip",                           // Имя ключа элемента данных
		"Возвращает IP-адрес узла сети.", // Описание ключа элемента данных
	)
	if err != nil {
		return errs.Wrap(err, "не удалось зарегистрировать метрики")
	}

	// Создайте новый обработчик.
	h, err := container.NewHandler("MyIP") // Имя плагина
	if err != nil {
		return errs.Wrap(err, "не удалось создать новый обработчик")
	}

	// Настройте журналирование для перенаправления журналов из плагина в агент.
	// Доступно через p.Logger.Infof, p.Logger.Debugf и т. д.
	p.Logger = h

	// Запустите выполнение плагина.
	// Блокируется до получения запроса на завершение от агента.
	err = h.Execute()
	if err != nil {
		return errs.Wrap(err, "не удалось выполнить обработчик плагина")
	}

	return nil
}

func (p *myIP) Export(
	key string, params []string, context plugin.ContextProvider,
) (any, error) {
	// Плагин может использовать разную логику сбора данных в зависимости от параметра `key`.
	// Эта реализация только проверяет, что указанный `key` поддерживается.
	if key != "myip" {
		return nil, errs.Errorf("unknown item key %q", key)
	}

	// Журнал будет перенаправлен в журнал агента 2.
	p.Infof(
		"получен запрос на обработку ключа %q с %d параметрами",
		key,
		len(params),
	)

	// Соберите данные и верните их.

	resp, err := http.Get("https://api.ipify.org")
	if err != nil {
		return nil, errs.Wrap(err, "не удалось получить IP-адрес")
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, errs.Wrap(err, "не удалось прочитать тело ответа")
	}

	return string(body), nil
}
```

[comment]: # ({/6aef1ccf-38f17bf9})

