[comment]: # ({f238de62-e03ef403})
# Ein Plugin erstellen (Tutorial)

Dies ist ein Schritt-für-Schritt-Tutorial, das zeigt, wie man ein einfaches ladbares Plugin für Zabbix Agent 2 erstellt.

Sie können auch das [Beispiel-Repository](https://git.zabbix.com/projects/AP/repos/example/browse) als Vorlage oder Leitfaden verwenden, um Ihre eigenen Plugins zu erstellen.

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

[comment]: # ({4aa59460-3b9b2b36})
### Was Sie erstellen werden

Dieses Tutorial zeigt, wie Sie ein neues ladbares Plugin **MyIP** erstellen.
Das Plugin implementiert einen einzelnen Datenpunkt-Schlüssel, **myip**, der die externe IP-Adresse des Hosts zurückgibt, auf dem der Zabbix Agent 2 ausgeführt wird.

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

[comment]: # ({bf3f1fff-47915903})
### Schritt 1: Einrichtung


1. Ein Plugin ist ein Standard-Go-Modul.
Beginnen Sie damit, die Datei `go.mod` im Plugin-Verzeichnis zu initialisieren, um die Abhängigkeiten des Plugins zu verfolgen:

```sh
cd path/to/plugins/myip # Wechseln Sie in Ihr Plugin-Verzeichnis
go mod init myip
```

2. Installieren Sie die erforderliche Abhängigkeit Zabbix Go SDK (`golang.zabbix.com/sdk`):

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

Ersetzen Sie `$LATEST_COMMIT_HASH` durch den neuesten `HEAD`-Commit-Hash aus dem [Repository](https://git.zabbix.com/projects/AP/repos/plugin-support/commits) von `golang.zabbix.com/sdk` im entsprechenden Release-Branch.
Zum Beispiel:

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

Beachten Sie, dass die Versionierung von `golang.zabbix.com/sdk` derzeit nicht unterstützt wird, sich dies jedoch in Zukunft ändern kann.

Zusätzliche Abhängigkeiten können bei Bedarf mit `go get` installiert werden.

3. Erstellen Sie eine leere Datei `main.go` für den Quellcode des Plugins:

```sh
touch main.go
```

Damit ist die erste Einrichtung abgeschlossen, und das Plugin ist bereit für die Entwicklung.

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

[comment]: # ({f8ebbdf8-a76d6ea7})
### Schritt 2: Plugin-Struktur

Das im vorherigen Schritt installierte Modul `golang.zabbix.com/sdk` stellt das notwendige Framework für die Plugin-Entwicklung bereit und sorgt dafür, dass alle Plugins eine einheitliche Struktur haben.

1. Grundlegenden Ausführungsablauf einrichten.

Beginnen Sie damit, den Hauptausführungsablauf des Plugins zu definieren. Fügen Sie den folgenden Code zu `main.go` hinzu:

```go
package main

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

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

Damit wird der grundlegende Ausführungsablauf für das Plugin festgelegt.
Die Funktion `run` wird später die Kernlogik des Plugins enthalten.

2. Die Plugin-Interfaces kennenlernen.

Ein Zabbix Agent 2-Plugin soll durch eine Struktur dargestellt werden, die Interfaces aus dem Paket `golang.zabbix.com/sdk/plugin` implementiert:

- *Accessor* - definiert grundlegende Methoden, die alle Plugins implementieren müssen, z. B. das Setzen des Plugin-Namens und die Behandlung von Timeouts für Datenpunkt-Schlüssel.
- Eines oder mehrere der folgenden funktionalen Plugin-Interfaces:
	- *Exporter* - führt eine Abfrage aus und gibt einen Wert (oder mehrere Werte), nichts oder einen Fehler zurück; wird häufig zusammen mit dem *Collector*-Interface verwendet.
	- *Collector* - verwaltet die periodische Erfassung von Daten.
	- *Runner* - definiert Start- und Beendigungsprozeduren des Plugins.
	- *Watcher* - ermöglicht die Implementierung einer unabhängigen Metrikabfrage unter Umgehung des internen Schedulers des Agenten; nützlich für trap-basierte oder ereignisgesteuerte Überwachung.
	- *Configurator* - definiert, wie das Plugin seine Konfigurationseinstellungen liest und anwendet.

Sie können diese Interfaces entweder selbst implementieren oder die vom Zabbix Go SDK bereitgestellten Standardimplementierungen verwenden und diese bei Bedarf anpassen. 
Dieses Tutorial verwendet die Standardimplementierungen.

3. Die Plugin-Struktur erstellen.

Importieren Sie nun das Paket *plugin* und erstellen Sie eine `myIP`-Struktur, die die Struktur `plugin.Base` einbettet:

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

type myIP struct {
	plugin.Base
}
```

Die Struktur `myIP` erfüllt derzeit das `Accessor`-Interface. 
Eine Methode zur Implementierung eines der funktionalen Plugin-Interfaces, des `Exporter`, wird später im Tutorial hinzugefügt.

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

[comment]: # ({c3726cdb-4c521593})
### Schritt 3: Datenpunkt-Schlüssel definieren

Ihr Plugin benötigt die Datenpunkt-Schlüssel, um Daten zu erfassen und an den Zabbix Server oder Proxy bereitzustellen.

1. Importieren Sie das Paket *errs* für die Fehlerbehandlung:

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

2. Registrieren Sie Datenpunkt-Schlüssel mit der Funktion `plugin.RegisterMetrics()` innerhalb der Funktion `run()`:

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

	// Registriert den Datenpunkt-Schlüssel `myip`.
	err := plugin.RegisterMetrics(
		p,
		"MyIP",                           // Plugin-Name
		"myip",                           // Datenpunkt-Schlüsselname
		"Returns the host's IP address.", // Beschreibung des Datenpunkt-Schlüssels
	)
	if err != nil {
		return errs.Wrap(err, "failed to register metrics")
	}

	return nil
}
```

Um mehrere Datenpunkt-Schlüssel zu registrieren, wiederholen Sie die Parameter *metric name* und *description* für jede Metrik.  
Zum Beispiel:

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

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

[comment]: # ({8227eb99-b7f436a7})
### Schritt 4: Den Handler einrichten

Der Handler erleichtert die Kommunikation zwischen dem Agent und dem Plugin.

1. Importieren Sie das *container*-Paket:

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

2. Fügen Sie innerhalb der Funktion `run()` Code hinzu, um einen Handler zu erstellen und einzurichten:

```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})
### Schritt 5: Datenerfassung implementieren

Die Datenerfassung erfolgt über die Exporter-Schnittstelle, die die Methode `Export` beschreibt:

```go
func Export(
  key string,             // Der Schlüssel des Datenpunkts, der erfasst werden soll.
  params []string,        // An den Datenpunktschlüssel übergebene Argumente (`myip[arg1, arg2]`).
  context ContextProvider // Metadaten zur Datenerfassung des Datenpunktschlüssels.
) (any, error)
```

1. Importieren Sie die erforderlichen Pakete für HTTP-Anfragen und das Einlesen von Antworten:

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

2. Implementieren Sie die Methode `Export` für die Struktur `myIP`:

```go
func (p *myIP) Export(
	key string, params []string, context plugin.ContextProvider,
) (any, error) {
	// Das Plugin kann je nach `key`-Parameter unterschiedliche Logik zur Datenerfassung verwenden.
    // Diese Implementierung prüft nur, ob der angegebene `key` unterstützt wird.
	if key != "myip" {
		return nil, errs.Errorf("unknown item key %q", key)
	}

	// Das Log wird an das Log von Agent 2 weitergeleitet.
	p.Infof(
		"received request to handle %q key with %d parameters",
		key,
		len(params),
	)

	// Erfassen Sie die Daten und geben Sie sie zurück.

	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})
### Schritt 6: Das Plugin erstellen und konfigurieren

1. Um das Plugin zu erstellen, führen Sie aus:

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

Dadurch sollte im aktuellen Verzeichnis ein ausführbares `myip` erstellt werden.

2. Konfigurieren Sie Zabbix Agent 2 so, dass das Plugin verwendet wird:

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

Ersetzen Sie `$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE` durch den Pfad zu dem in Schritt 5 erstellten `myip`.

Der Plugin-Name im Namen des Konfigurationsparameters (_MyIP_ in diesem Tutorial) muss mit dem in der Funktion _plugin.RegisterMetrics()_ definierten Plugin-Namen übereinstimmen.

3. Um das Plugin und seinen `myip`-Datenpunkt zu testen, führen Sie aus:

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

Die Ausgabe sollte eine externe IP-Adresse Ihres Hosts enthalten und etwa so aussehen:

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

Damit haben Sie ein einfaches ladbares Plugin für Zabbix Agent 2 erstellt.
Glückwunsch!

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

[comment]: # ({6aef1ccf-38f17bf9})
### Vollständiger Quellcode

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

	// Registrieren Sie den `myip` Datenpunkt-Schlüssel.
	err := plugin.RegisterMetrics(
		p,
		"MyIP",                           // Name des Plugins
		"myip",                           // Name des Datenpunkt-Schlüssels
		"Gibt die IP-Adresse des Hosts zurück.", // Beschreibung des Datenpunkt-Schlüssels
	)
	if err != nil {
		return errs.Wrap(err, "Fehler beim Registrieren der Metriken")
	}

	// Einen neuen Handler erstellen.
	h, err := container.NewHandler("MyIP") // Name des Plugins
	if err != nil {
		return errs.Wrap(err, "Fehler beim Erstellen eines neuen Handlers")
	}

	// Logging einrichten, um Protokolle vom Plugin an den Agent weiterzuleiten.
	// Verfügbar über p.Logger.Infof, p.Logger.Debugf usw.
	p.Logger = h

	// Die Ausführung des Plugins starten.
	// Blockiert, bis eine Beendigungsanforderung vom Agent empfangen wird.
	err = h.Execute()
	if err != nil {
		return errs.Wrap(err, "Fehler bei der Ausführung des Plugin-Handlers")
	}

	return nil
}

func (p *myIP) Export(
	key string, params []string, context plugin.ContextProvider,
) (any, error) {
	// Das Plugin kann je nach `key`-Parameter unterschiedliche Datenerfassungslogik verwenden.  
    // Diese Implementierung prüft nur, ob der angegebene `key` unterstützt wird. 
	if key != "myip" {
		return nil, errs.Errorf("unbekannter Datenpunkt-Schlüssel %q", key)
	}

	// Das Protokoll wird an das Log des Agent 2 weitergeleitet.
	p.Infof(
		"Anforderung zum Verarbeiten des Schlüssels %q mit %d Parametern empfangen",
		key,
		len(params),
	)

	// Die Daten erfassen und zurückgeben.

	resp, err := http.Get("https://api.ipify.org")
	if err != nil {
		return nil, errs.Wrap(err, "Fehler beim Abrufen der IP-Adresse")
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, errs.Wrap(err, "Fehler beim Lesen des Antwortinhalts")
	}

	return string(body), nil
}
```

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

