[comment]: # ({f238de62-e03ef403})
# Stwórz wtyczkę (samouczek)

To jest samouczek, który  krok po kroku pokaże jak utworzyć prostą ładowalną wtyczkę dla agenta Zabbix 2.

Możesz też używać naszego [przykładowego repozytorium](https://git.zabbix.com/projects/AP/repos/example/browse) jako szablonu lub przewodnika do tworzenia własnych wtyczek.

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

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

Ten samouczek pokazuje, jak utworzyć nową wtyczkę **MyIP**. Wtyczka zaimplementuje pojedynczy klucz pozycji, **myip**, który zwraca zewnętrzny adres IP hosta, na którym uruchomiony jest agent Zabbix 2.

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

[comment]: # ({bf3f1fff-47915903})
### Krok 1: Konfiguracja


1. Plugin jest standardowym modułem Go.
Zacznij od zainicjowania pliku `go.mod` w katalogu pluginu, aby śledzić zależności pluginu: 

```sh
cd path/to/plugins/myip # Przełącz się do katalogu swojego pluginu
go mod init myip
```

2. Zainstaluj obowiązkową zależność Zabbix Go SDK (`golang.zabbix.com/sdk`):

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

Zastąp `$LATEST_COMMIT_HASH` najnowszym hashem commita `HEAD` z repozytorium `golang.zabbix.com/sdk` [repository](https://git.zabbix.com/projects/AP/repos/plugin-support/commits) w odpowiedniej gałęzi wydania.
Na przykład:

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

Należy pamiętać, że wersjonowanie `golang.zabbix.com/sdk` nie jest obecnie obsługiwane, ale może się to zmienić w przyszłości.

W razie potrzeby można zainstalować dodatkowe zależności za pomocą `go get`.

3. Utwórz pusty plik `main.go` dla kodu źródłowego pluginu:

```sh
touch main.go
```

Teraz wstępna konfiguracja jest zakończona, a plugin jest gotowy do dalszego rozwoju.

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

[comment]: # ({f8ebbdf8-a76d6ea7})
### Krok 2: Struktura pluginu

Moduł `golang.zabbix.com/sdk`, zainstalowany w poprzednim kroku, zapewnia niezbędne ramy do tworzenia pluginów i gwarantuje, że wszystkie pluginy mają spójną strukturę.

1. Skonfiguruj podstawowy przepływ wykonywania.

Zacznij od zdefiniowania głównego przepływu wykonywania pluginu. Dodaj poniższy kod do `main.go`:

```go
package main

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

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

To ustanawia podstawowy przepływ wykonywania dla pluginu.
Funkcja run będzie później zawierać główną logikę pluginu.

2. Poznaj interfejsy pluginu.

Plugin Zabbix agent 2 powinien być reprezentowany przez strukturę, która implementuje interfejsy z pakietu `golang.zabbix.com/sdk/plugin`:

- *Accessor* - definiuje podstawowe metody, które wszystkie pluginy muszą implementować, takie jak ustawianie nazwy pluginu i obsługa limitów czasu dla kluczy pozycji.
- Jeden lub więcej z następujących funkcjonalnych interfejsów pluginu:
	- *Exporter* - wykonuje odpytywanie i zwraca wartość (lub wartości), nic albo błąd; często używany razem z interfejsem *Collector*.
	- *Collector* - zarządza okresowym zbieraniem danych.
	- *Runner* - definiuje procedury uruchamiania i zamykania pluginu.
	- *Watcher* - umożliwia implementację niezależnego odpytywania metryk z pominięciem wewnętrznego harmonogramu agenta; przydatny w monitorowaniu opartym na pułapkach lub zdarzeniach.
	- *Configurator* - definiuje sposób, w jaki plugin odczytuje i stosuje swoje ustawienia konfiguracyjne.

Możesz zaimplementować te interfejsy samodzielnie albo użyć domyślnych, dostarczonych przez Zabbix Go SDK, modyfikując je w razie potrzeby. 
Ten samouczek korzysta z domyślnych implementacji.

3. Utwórz strukturę pluginu.

Teraz zaimportuj pakiet *plugin* i utwórz strukturę `myIP`, która osadza strukturę `plugin.Base`:

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

type myIP struct {
	plugin.Base
}
```

Struktura myIP obecnie spełnia interfejs Accessor. 
Metoda implementująca jeden z funkcjonalnych interfejsów pluginu, `Exporter`, zostanie dodana później w samouczku.

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

[comment]: # ({c3726cdb-4c521593})
### Krok 3: Zdefiniuj klucze pozycji

Twój plugin potrzebuje kluczy pozycji, aby zbierać dane i przekazywać je do serwer lub proxy Zabbix.

1. Zaimportuj pakiet *errs* do obsługi błędów:

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

2. Zarejestruj klucze pozycji za pomocą funkcji `plugin.RegisterMetrics()` wewnątrz funkcji `run()`:

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

	// Zarejestruj klucz pozycji `myip`.
	err := plugin.RegisterMetrics(
		p,
		"MyIP",                           // Nazwa pluginu
		"myip",                           // Nazwa klucza pozycji
		"Zwraca adres IP hosta.",         // Opis klucza pozycji
	)
	if err != nil {
		return errs.Wrap(err, "nie udało się zarejestrować metryk")
	}

	return nil
}
```

Aby zarejestrować kilka kluczy pozycji, powtórz parametry *nazwa metryki* i *opis* dla każdej metryki. 
Na przykład:

```go
plugin.RegisterMetrics(&impl, "Myip", "metric.one", "Opis metryki pierwszej.", "metric.two", "Opis metryki drugiej.")
```

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

[comment]: # ({8227eb99-b7f436a7})
### Krok 4: Skonfiguruj handler

Handler ułatwia komunikację między agentem a wtyczką.

1. Zaimportuj pakiet *container*:

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

2. Wewnątrz funkcji `run()` dodaj kod, aby utworzyć i skonfigurować handler:

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

	// Zarejestruj klucz pozycji `myip`.
	err := plugin.RegisterMetrics(
		p,
		"MyIP",                           // Nazwa wtyczki
		"myip",                           // Nazwa klucza pozycji
		"Zwraca adres IP hosta.",         // Opis klucza pozycji
	)
	if err != nil {
		return errs.Wrap(err, "failed to register metrics")
	}

	// Utwórz nowy handler.
	h, err := container.NewHandler("MyIP") // Nazwa wtyczki
	if err != nil {
		return errs.Wrap(err, "failed to create new handler")
	}

	// Skonfiguruj logowanie, aby przekazywać logi z wtyczki do agenta.
	// Dostępne przez p.Logger.Infof, p.Logger.Debugf itd.
	p.Logger = h

	// Uruchom wykonanie wtyczki.
	// Blokuje do momentu otrzymania żądania zakończenia od agenta.
	err = h.Execute()
	if err != nil {
		return errs.Wrap(err, "failed to execute plugin handler")
	}

	return nil
}
```

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

[comment]: # ({d6f0f4ab-c7541684})
### Krok 5: Implementacja zbierania danych

Zbieranie danych odbywa się za pośrednictwem interfejsu Exporter, który opisuje metodę `Export`:

```go
func Export(
  key string,             // Klucz pozycji do zebrania.
  params []string,        // Argumenty przekazane do klucza pozycji (`myip[arg1, arg2]`).
  context ContextProvider // Metadane dotyczące zbierania danych dla klucza pozycji.
) (any, error)
```

1. Zaimportuj wymagane pakiety do obsługi żądań HTTP i odczytu odpowiedzi:

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

2. Zaimplementuj metodę `Export` dla struktury `myIP`:

```go
func (p *myIP) Export(
	key string, params []string, context plugin.ContextProvider,
) (any, error) {
	// Wtyczka może używać różnych metod zbierania danych w zależności od parametru `key`.
    // Ta implementacja jedynie sprawdza, czy podany `key` jest obsługiwany.
	if key != "myip" {
		return nil, errs.Errorf("unknown item key %q", key)
	}

	// Log zostanie przekazany do logu agent 2.
	p.Infof(
		"received request to handle %q key with %d parameters",
		key,
		len(params),
	)

	// Zbierz dane i zwróć wynik.

	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})
### Krok 6: Zbuduj i skonfiguruj plugin

1. Aby zbudować plugin, uruchom:

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

To powinno utworzyć plik wykonywalny `myip` w bieżącym katalogu.

2. Skonfiguruj agent Zabbix 2, aby używał pluginu:

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

Zastąp `$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE` ścieżką do `myip` utworzonego w kroku 5.

Nazwa pluginu w nazwie parametru konfiguracji (_MyIP_ w tym samouczku) musi być zgodna z nazwą pluginu zdefiniowaną w funkcji _plugin.RegisterMetrics()_.

3. Aby przetestować plugin i jego pozycję `myip`, uruchom:

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

Wynik powinien zawierać zewnętrzny adres IP twojego hosta i wyglądać podobnie do tego:

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

W ten sposób utworzyłeś prosty, ładowalny plugin dla agenta Zabbix 2.
Gratulacje!

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

[comment]: # ({6aef1ccf-38f17bf9})
### Kompletny kod źródłowy

```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{}

// Zarejestruj klucz elementu `myip`.
err := plugin.RegisterMetrics(
p,
"MyIP", // Nazwa wtyczki
"myip", // Nazwa klucza pozycji
"Zwraca adres IP hosta.", // Opis klucza pozycji
)
if err != nil {
return errs.Wrap(err, "nie udało się zarejestrować metryk")
}

// Utwórz nowy program obsługi.
h, err := container.NewHandler("MyIP") // Nazwa wtyczki
if err != nil {
return errs.Wrap(err, "nie udało się utworzyć nowego programu obsługi")
}

// Skonfiguruj rejestrowanie, aby przekazywać logi z wtyczki do agenta.
// Dostępne za pośrednictwem p.Logger.Infof, p.Logger.Debugf itp.
p.Logger = h

// Rozpocznij wykonywanie wtyczki.
// Blokuje do momentu otrzymania żądania zakończenia od agenta.
err = h.Execute()
if err != nil {
return errs.Wrap(err, "nie udało się wykonać obsługi wtyczki")
}

return nil
}

func (p *myIP) Export(
key string, params []string, context plugin.ContextProvider,
) (any, error) {
// Wtyczka może używać innej logiki zbierania danych w oparciu o parametr `key`. 
// Ta implementacja weryfikuje tylko, czy podany `key` jest obsługiwany. 
if key != "myip" {
return nil, errs.Errorf("nieznany klucz elementu %q", key)
}

// Dziennik zostanie przesłany do dziennika agenta 2.
p.Infof(
"otrzymano żądanie obsługi klucza %q z %d parametrami",
key,
len(params),
)

// Zbierz dane i zwróć je.

resp, err := http.Get("https://api.ipify.org")
if err != nil {
return nil, errs.Wrap(err, "nie udało się pobrać adresu IP")
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errs.Wrap(err, "nie udało się odczytać treści odpowiedzi")
}

return string(body), nil
}
```

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

