feat: generate the plugins sample config (#10886)

This commit is contained in:
Sebastian Spaink 2022-04-05 17:11:09 -05:00 committed by GitHub
parent 6cb25207fd
commit 5d6748fcb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 389 additions and 117 deletions

View File

@ -78,6 +78,7 @@ commands:
key: windows-go-<< parameters.cache_version >>-{{ checksum "go.sum" }}
- run: 'sh ./scripts/installgo_windows.sh'
- run: choco install mingw
- run: 'make generate'
- run: mkdir -p test-results
- run: ./scripts/install_gotestsum.sh << parameters.os >> << parameters.gotestsum >>
- unless:
@ -120,7 +121,7 @@ commands:
paths:
- 'C:\Go'
- 'C:\Users\circleci\project\gotestsum.exe'
- run: 'make generate-clean'
package-build:
parameters:
type:

View File

@ -109,10 +109,21 @@ versioninfo:
go run scripts/generate_versioninfo/main.go; \
go generate cmd/telegraf/telegraf_windows.go; \
.PHONY: telegraf
telegraf:
.PHONY: generate
generate:
go generate -run="plugindata/main.go$$" ./plugins/inputs/... ./plugins/outputs/... ./plugins/processors/... ./plugins/aggregators/...
.PHONY: generate-clean
generate-clean:
go generate -run="plugindata/main.go --clean" ./plugins/inputs/... ./plugins/outputs/... ./plugins/processors/... ./plugins/aggregators/...
.PHONY: build
build:
go build -ldflags "$(LDFLAGS)" ./cmd/telegraf
.PHONY: telegraf
telegraf: generate build generate-clean
# Used by dockerfile builds
.PHONY: go-install
go-install:
@ -312,7 +323,7 @@ darwin-arm64:
include_packages := $(mips) $(mipsel) $(arm64) $(amd64) $(static) $(armel) $(armhf) $(riscv64) $(s390x) $(ppc64le) $(i386) $(windows) $(darwin-amd64) $(darwin-arm64)
.PHONY: package
package: $(include_packages)
package: generate $(include_packages) generate-clean
.PHONY: $(include_packages)
$(include_packages):

View File

@ -678,23 +678,24 @@ func printConfig(name string, p telegraf.PluginDescriber, op string, commented b
if commented {
comment = "# "
}
fmt.Printf("\n%s# %s\n%s[[%s.%s]]", comment, p.Description(), comment, op, name)
if di.Since != "" {
removalNote := ""
if di.RemovalIn != "" {
removalNote = " and will be removed in " + di.RemovalIn
}
fmt.Printf("\n%s ## DEPRECATED: The '%s' plugin is deprecated in version %s%s, %s.", comment, name, di.Since, removalNote, di.Notice)
fmt.Printf("\n%s ## DEPRECATED: The '%s' plugin is deprecated in version %s%s, %s.", comment, name, di.Since, removalNote, di.Notice)
}
config := p.SampleConfig()
if config == "" {
fmt.Printf("\n#[[%s.%s]]", op, name)
fmt.Printf("\n%s # no configuration\n\n", comment)
} else {
lines := strings.Split(config, "\n")
fmt.Print("\n")
for i, line := range lines {
if i == 0 || i == len(lines)-1 {
if i == len(lines)-1 {
fmt.Print("\n")
continue
}

View File

@ -678,7 +678,6 @@ type MockupInputPluginParserOld struct {
}
func (m *MockupInputPluginParserOld) SampleConfig() string { return "Mockup old parser test plugin" }
func (m *MockupInputPluginParserOld) Description() string { return "Mockup old parser test plugin" }
func (m *MockupInputPluginParserOld) Gather(acc telegraf.Accumulator) error { return nil }
func (m *MockupInputPluginParserOld) SetParser(parser parsers.Parser) { m.Parser = parser }
func (m *MockupInputPluginParserOld) SetParserFunc(f parsers.ParserFunc) { m.ParserFunc = f }
@ -690,7 +689,6 @@ type MockupInputPluginParserNew struct {
}
func (m *MockupInputPluginParserNew) SampleConfig() string { return "Mockup old parser test plugin" }
func (m *MockupInputPluginParserNew) Description() string { return "Mockup old parser test plugin" }
func (m *MockupInputPluginParserNew) Gather(acc telegraf.Accumulator) error { return nil }
func (m *MockupInputPluginParserNew) SetParser(parser telegraf.Parser) { m.Parser = parser }
func (m *MockupInputPluginParserNew) SetParserFunc(f telegraf.ParserFunc) { m.ParserFunc = f }
@ -714,7 +712,6 @@ type MockupInputPlugin struct {
}
func (m *MockupInputPlugin) SampleConfig() string { return "Mockup test input plugin" }
func (m *MockupInputPlugin) Description() string { return "Mockup test input plugin" }
func (m *MockupInputPlugin) Gather(acc telegraf.Accumulator) error { return nil }
func (m *MockupInputPlugin) SetParser(parser telegraf.Parser) { m.parser = parser }
@ -730,7 +727,6 @@ type MockupOuputPlugin struct {
func (m *MockupOuputPlugin) Connect() error { return nil }
func (m *MockupOuputPlugin) Close() error { return nil }
func (m *MockupOuputPlugin) Description() string { return "Mockup test output plugin" }
func (m *MockupOuputPlugin) SampleConfig() string { return "Mockup test output plugin" }
func (m *MockupOuputPlugin) Write(metrics []telegraf.Metric) error { return nil }

View File

@ -9,10 +9,10 @@ This section is for developers who want to create a new aggregator plugin.
register themselves. See below for a quick example.
* To be available within Telegraf itself, plugins must add themselves to the
`github.com/influxdata/telegraf/plugins/aggregators/all/all.go` file.
* The `SampleConfig` function should return valid toml that describes how the
plugin can be configured. This is included in `telegraf config`. Please
consult the [Sample Config][] page for the latest style guidelines.
* The `Description` function should say in one line what this aggregator does.
* Each plugin requires a file called `<plugin_name>_sample_config.go`, where `<plugin_name>` is replaced with the actual plugin name.
Copy the [example template](#sample-configuration-template) into this file, also updating `<plugin_name>` were appropriate.
This file is automatically updated during the build process to include the sample configuration from the `README.md`.
Please consult the [Sample Config][] page for the latest style guidelines.
* The Aggregator plugin will need to keep caches of metrics that have passed
through it. This should be done using the builtin `HashID()` function of
each metric.
@ -22,6 +22,8 @@ This section is for developers who want to create a new aggregator plugin.
### Aggregator Plugin Example
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
package min
// min.go
@ -44,26 +46,10 @@ func NewMin() telegraf.Aggregator {
return m
}
var sampleConfig = `
## period is the flush & clear interval of the aggregator.
period = "30s"
## If true drop_original will drop the original metrics and
## only send aggregates.
drop_original = false
`
func (m *Min) Init() error {
return nil
}
func (m *Min) SampleConfig() string {
return sampleConfig
}
func (m *Min) Description() string {
return "Keep the aggregate min of each metric passing through."
}
func (m *Min) Add(in telegraf.Metric) {
id := in.HashID()
if _, ok := m.nameCache[id]; !ok {
@ -127,6 +113,19 @@ func init() {
}
```
### Sample Configuration Template
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
// DON'T EDIT; This file is used as a template by tools/generate_plugindata
package <plugin_package>
func (k *<plugin_struct>) SampleConfig() string {
return `{{ .SampleConfig }}`
}
```
[telegraf.Aggregator]: https://godoc.org/github.com/influxdata/telegraf#Aggregator
[Sample Config]: https://github.com/influxdata/telegraf/blob/master/docs/developers/SAMPLE_CONFIG.md
[Code Style]: https://github.com/influxdata/telegraf/blob/master/docs/developers/CODE_STYLE.md

View File

@ -15,11 +15,10 @@ and submit new inputs.
themselves. See below for a quick example.
- Input Plugins must be added to the
`github.com/influxdata/telegraf/plugins/inputs/all/all.go` file.
- The `SampleConfig` function should return valid toml that describes how the
plugin can be configured. This is included in `telegraf config`. Please
consult the [Sample Config][] page for the latest style
guidelines.
- The `Description` function should say in one line what this plugin does.
- Each plugin requires a file called `<plugin_name>_sample_config.go`, where `<plugin_name>` is replaced with the actual plugin name.
Copy the [example template](#sample-configuration-template) into this file, also updating `<plugin_name>` were appropriate.
This file is automatically updated during the build process to include the sample configuration from the `README.md`.
Please consult the [Sample Config][] page for the latest style guidelines.
- Follow the recommended [Code Style][].
Let's say you've written a plugin that emits metrics about processes on the
@ -28,10 +27,10 @@ current host.
## Input Plugin Example
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
package simple
// simple.go
import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
@ -42,17 +41,6 @@ type Simple struct {
Log telegraf.Logger `toml:"-"`
}
func (s *Simple) Description() string {
return "a demo plugin"
}
func (s *Simple) SampleConfig() string {
return `
## Indicate if everything is fine
ok = true
`
}
// Init is for setup, and validating config.
func (s *Simple) Init() error {
return nil
@ -73,6 +61,17 @@ func init() {
}
```
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
// DON'T EDIT; This file is used as a template by tools/generate_plugindata
package <plugin_package>
func (k *<plugin_struct>) SampleConfig() string {
return `{{ .SampleConfig }}`
}
```
### Development
- Run `make static` followed by `make plugin-[pluginName]` to spin up a docker
@ -101,7 +100,7 @@ You can then utilize the parser internally in your plugin, parsing data as you
see fit. Telegraf's configuration layer will take care of instantiating and
creating the `Parser` object.
Add the following to the `SampleConfig()`:
Add the following to the sample configuration in the README.md:
```toml
## Data format to consume.

View File

@ -11,15 +11,17 @@ similar constructs.
themselves. See below for a quick example.
- To be available within Telegraf itself, plugins must add themselves to the
`github.com/influxdata/telegraf/plugins/outputs/all/all.go` file.
- The `SampleConfig` function should return valid toml that describes how the
plugin can be configured. This is included in `telegraf config`. Please
consult the [Sample Config][] page for the latest style guidelines.
- The `Description` function should say in one line what this output does.
- Each plugin requires a file called `<plugin_name>_sample_config.go`, where `<plugin_name>` is replaced with the actual plugin name.
Copy the [example template](#sample-configuration-template) into this file, also updating `<plugin_name>` were appropriate.
This file is automatically updated during the build process to include the sample configuration from the `README.md`.
Please consult the [Sample Config][] page for the latest style guidelines.
- Follow the recommended [Code Style][].
## Output Plugin Example
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
package simpleoutput
// simpleoutput.go
@ -34,16 +36,6 @@ type Simple struct {
Log telegraf.Logger `toml:"-"`
}
func (s *Simple) Description() string {
return "a demo output"
}
func (s *Simple) SampleConfig() string {
return `
ok = true
`
}
// Init is for setup, and validating config.
func (s *Simple) Init() error {
return nil
@ -76,6 +68,19 @@ func init() {
```
### Sample Configuration Template
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
// DON'T EDIT; This file is used as a template by tools/generate_plugindata
package <plugin_package>
func (k *<plugin_struct>) SampleConfig() string {
return `{{ .SampleConfig }}`
}
```
## Data Formats
Some output plugins, such as the [file][] plugin, can write in any supported

View File

@ -9,18 +9,17 @@ This section is for developers who want to create a new processor plugin.
themselves. See below for a quick example.
* To be available within Telegraf itself, plugins must add themselves to the
`github.com/influxdata/telegraf/plugins/processors/all/all.go` file.
* The `SampleConfig` function should return valid toml that describes how the
processor can be configured. This is include in the output of `telegraf
config`.
* The `SampleConfig` function should return valid toml that describes how the
plugin can be configured. This is included in `telegraf config`. Please
consult the [Sample Config][] page for the latest style guidelines.
* The `Description` function should say in one line what this processor does.
* Each plugin requires a file called `<plugin_name>_sample_config.go`, where `<plugin_name>` is replaced with the actual plugin name.
Copy the [example template](#sample-configuration-template) into this file, also updating `<plugin_name>` were appropriate.
This file is automatically updated during the build process to include the sample configuration from the `README.md`.
Please consult the [Sample Config][] page for the latest style guidelines.
* Follow the recommended [Code Style][].
## Processor Plugin Example
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
package printer
// printer.go
@ -36,17 +35,6 @@ type Printer struct {
Log telegraf.Logger `toml:"-"`
}
var sampleConfig = `
`
func (p *Printer) SampleConfig() string {
return sampleConfig
}
func (p *Printer) Description() string {
return "Print all metrics that pass through this filter."
}
// Init is for setup, and validating config.
func (p *Printer) Init() error {
return nil
@ -66,6 +54,19 @@ func init() {
}
```
### Sample Configuration Template
```go
//go:generate go run ../../../tools/generate_plugindata/main.go
//go:generate go run ../../../tools/generate_plugindata/main.go --clean
// DON'T EDIT; This file is used as a template by tools/generate_plugindata
package <plugin_package>
func (k *<plugin_struct>) SampleConfig() string {
return `{{ .SampleConfig }}`
}
```
## Streaming Processors
Streaming processors are a new processor type available to you. They are
@ -102,17 +103,6 @@ type Printer struct {
Log telegraf.Logger `toml:"-"`
}
var sampleConfig = `
`
func (p *Printer) SampleConfig() string {
return sampleConfig
}
func (p *Printer) Description() string {
return "Print all metrics that pass through this filter."
}
// Init is for setup, and validating config.
func (p *Printer) Init() error {
return nil

View File

@ -1,7 +1,6 @@
# Sample Configuration
The sample config file is generated from a results of the `SampleConfig()` and
`Description()` functions of the plugins.
The sample config file is generated from a results of the `SampleConfig()` functions of the plugin.
You can generate a full sample
config:

1
go.mod
View File

@ -137,6 +137,7 @@ require (
github.com/wavefronthq/wavefront-sdk-go v0.9.10
github.com/wvanbergen/kafka v0.0.0-20171203153745-e2edea948ddf
github.com/xdg/scram v1.0.3
github.com/yuin/goldmark v1.4.1
go.mongodb.org/mongo-driver v1.8.3
go.opentelemetry.io/collector/model v0.44.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0

1
go.sum
View File

@ -2261,6 +2261,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20200603152657-dc2b0ca8b37e/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=

View File

@ -26,11 +26,8 @@ type Initializer interface {
// not part of the interface, but will receive an injected logger if it's set.
// eg: Log telegraf.Logger `toml:"-"`
type PluginDescriber interface {
// SampleConfig returns the default configuration of the Processor
// SampleConfig returns the default configuration of the Plugin
SampleConfig() string
// Description returns a one-sentence description on the Processor
Description() string
}
// Logger defines an plugin-related interface for logging.

View File

@ -34,16 +34,6 @@ func TestLoadConfig(t *testing.T) {
require.Equal(t, `test"\test`, inp.SecretValue)
}
func TestDefaultImportedPluginsSelfRegisters(t *testing.T) {
inputs.Add("test", func() telegraf.Input {
return &testInput{}
})
cfg, err := LoadConfig(nil)
require.NoError(t, err)
require.Equal(t, "test", cfg.Input.Description())
}
func TestLoadingSpecialTypes(t *testing.T) {
inputs.Add("test", func() telegraf.Input {
return &testDurationInput{}

View File

@ -66,10 +66,6 @@ func (i *erroringInput) SampleConfig() string {
return ""
}
func (i *erroringInput) Description() string {
return ""
}
func (i *erroringInput) Gather(acc telegraf.Accumulator) error {
acc.AddError(errors.New("intentional"))
return nil

View File

@ -24,10 +24,6 @@ func (sp *streamingProcessor) SampleConfig() string {
return sp.processor.SampleConfig()
}
func (sp *streamingProcessor) Description() string {
return sp.processor.Description()
}
func (sp *streamingProcessor) Start(acc telegraf.Accumulator) error {
sp.acc = acc
return nil

View File

@ -0,0 +1,157 @@
// generate_plugindata is a tool used to inject the sample configuration into all the plugins
// It extracts the sample configuration from the plugins README.md
// Then using the file plugin_name_sample_config.go as a template, and will be updated with the sample configuration
// This tool is then also used to revert these changes with the `--clean` flag
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"log" //nolint:revive
"os"
"strings"
"text/template"
"github.com/yuin/goldmark"
gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
)
func createSourceName(packageName string) string {
return fmt.Sprintf("%s_sample_config.go", packageName)
}
// extractPluginData reads the README.md to get the sample configuration
func extractPluginData() (string, error) {
readMe, err := os.ReadFile("README.md")
if err != nil {
return "", err
}
p := goldmark.DefaultParser()
r := text.NewReader(readMe)
root := p.Parse(r)
var currentSection string
for n := root.FirstChild(); n != nil; n = n.NextSibling() {
switch tok := n.(type) {
case *gast.Heading:
if tok.FirstChild() != nil {
currentSection = string(tok.FirstChild().Text(readMe))
}
case *gast.FencedCodeBlock:
if currentSection == "Configuration" && string(tok.Language(readMe)) == "toml" {
var config []byte
for i := 0; i < tok.Lines().Len(); i++ {
line := tok.Lines().At(i)
config = append(config, line.Value(readMe)...)
}
return string(config), nil
}
}
}
fmt.Printf("No configuration found for plugin: %s\n", os.Getenv("GOPACKAGE"))
return "", nil
}
// generatePluginData parses the main source file of the plugin as a template and updates it with the sample configuration
// The original source file is saved so that these changes can be reverted
func generatePluginData(packageName string, sampleConfig string) error {
sourceName := createSourceName(packageName)
plugin, err := os.ReadFile(sourceName)
if err != nil {
return err
}
generatedTemplate := template.Must(template.New("").Parse(string(plugin)))
f, err := os.Create(sourceName)
if err != nil {
return err
}
defer f.Close()
err = generatedTemplate.Execute(f, struct {
SampleConfig string
}{
SampleConfig: sampleConfig,
})
if err != nil {
return err
}
return nil
}
var newSampleConfigFunc = ` return ` + "`{{ .SampleConfig }}`\n"
// cleanGeneratedFiles will revert the changes made by generatePluginData
func cleanGeneratedFiles(packageName string) error {
sourceName := createSourceName(packageName)
sourcefile, err := os.Open(sourceName)
if err != nil {
return err
}
defer sourcefile.Close()
var c []byte
buf := bytes.NewBuffer(c)
scanner := bufio.NewScanner(sourcefile)
var sampleconfigSection bool
for scanner.Scan() {
if sampleconfigSection && strings.TrimSpace(scanner.Text()) == "}" {
sampleconfigSection = false
if _, err := buf.Write([]byte(newSampleConfigFunc)); err != nil {
return err
}
}
if !sampleconfigSection {
if _, err := buf.Write(scanner.Bytes()); err != nil {
return err
}
if _, err = buf.WriteString("\n"); err != nil {
return err
}
}
if !sampleconfigSection && strings.Contains(scanner.Text(), "SampleConfig() string") {
sampleconfigSection = true
}
}
err = os.WriteFile(sourceName, buf.Bytes(), 0664)
if err != nil {
return err
}
return nil
}
func main() {
clean := flag.Bool("clean", false, "Remove generated files")
flag.Parse()
goPackage := os.Getenv("GOPACKAGE")
if *clean {
err := cleanGeneratedFiles(goPackage)
if err != nil {
log.Fatal(err)
}
} else {
s, err := extractPluginData()
if err != nil {
log.Fatal(err)
}
err = generatePluginData(goPackage, s)
if err != nil {
log.Fatal(err)
}
}
}

View File

@ -0,0 +1,133 @@
package main
import (
"os"
"testing"
"github.com/stretchr/testify/require"
)
var originalPlugin = `package main
func (*Plugin) SampleConfig() string {
return ` + "`{{ .SampleConfig }}`" + `
}
`
func TestGeneratePluginData(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
readme := `# plugin
## Configuration
` + "```" + `toml
# test plugin
[[input.plugin]]
# No configuration
` + "```"
r, err := os.Create("README.md")
require.NoError(t, err)
_, err = r.Write([]byte(readme))
require.NoError(t, err)
err = r.Close()
require.NoError(t, err)
sourceFile, err := os.Create("test_sample_config.go")
require.NoError(t, err)
_, err = sourceFile.Write([]byte(originalPlugin))
require.NoError(t, err)
err = sourceFile.Close()
require.NoError(t, err)
defer func() {
err = os.Remove("test_sample_config.go")
require.NoError(t, err)
err = os.Remove("README.md")
require.NoError(t, err)
}()
s, err := extractPluginData()
require.NoError(t, err)
err = generatePluginData("test", s)
require.NoError(t, err)
expected := `package main
func (*Plugin) SampleConfig() string {
return ` + "`" + `# test plugin
[[input.plugin]]
# No configuration
` + "`" + `
}
`
newSourceFile, err := os.ReadFile("test_sample_config.go")
require.NoError(t, err)
require.Equal(t, expected, string(newSourceFile))
}
func TestGeneratePluginDataNoConfig(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
readme := `# plugin`
r, err := os.Create("README.md")
require.NoError(t, err)
_, err = r.Write([]byte(readme))
require.NoError(t, err)
err = r.Close()
require.NoError(t, err)
defer func() {
err = os.Remove("README.md")
require.NoError(t, err)
}()
s, err := extractPluginData()
require.NoError(t, err)
require.Empty(t, s)
}
func setupGeneratedPluginFile(t *testing.T, fileName string) {
// Create files that will be cleaned up
r, err := os.Create(fileName)
require.NoError(t, err)
defer r.Close()
updatePlugin := `package main
func (*Plugin) SampleConfig() string {
return "I am a sample config"
}
`
_, err = r.Write([]byte(updatePlugin))
require.NoError(t, err)
}
func TestCleanGeneratedFiles(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
filename := "testClean_sample_config.go"
setupGeneratedPluginFile(t, filename)
err := cleanGeneratedFiles("testClean")
require.NoError(t, err)
b, err := os.ReadFile(filename)
require.NoError(t, err)
require.Equal(t, originalPlugin, string(b))
err = os.Remove(filename)
require.NoError(t, err)
}