Skip to content

Support multiple sample events #1423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,5 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

replace github.com/elastic/package-spec/v2 => github.com/jsoriano/package-spec/v2 v2.0.0-20230830222135-333f997ace72
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ github.com/elastic/go-ucfg v0.8.6 h1:stUeyh2goTgGX+/wb9gzKvTv0YB0231LTpKUgCKj4U0
github.com/elastic/go-ucfg v0.8.6/go.mod h1:4E8mPOLSUV9hQ7sgLEJ4bvt0KhMuDJa8joDT2QGAEKA=
github.com/elastic/gojsonschema v1.2.1 h1:cUMbgsz0wyEB4x7xf3zUEvUVDl6WCz2RKcQPul8OsQc=
github.com/elastic/gojsonschema v1.2.1/go.mod h1:biw5eBS2Z4T02wjATMRSfecfjCmwaDPvuaqf844gLrg=
github.com/elastic/package-spec/v2 v2.10.0 h1:p/T/xgue/7eJW3drXqaZ/Zsjbrc2hT2w652OJ7jEmmY=
github.com/elastic/package-spec/v2 v2.10.0/go.mod h1:iQ/8tKtmsIUI9zXM2x2punKb77fSX1FPcChDr8zXPR8=
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
Expand Down Expand Up @@ -232,6 +230,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jsoriano/package-spec/v2 v2.0.0-20230830222135-333f997ace72 h1:qaSoeqMpC9p8AIA1HvtOUWW9zbFGxPakZftHyscgBjg=
github.com/jsoriano/package-spec/v2 v2.0.0-20230830222135-333f997ace72/go.mod h1:iQ/8tKtmsIUI9zXM2x2punKb77fSX1FPcChDr8zXPR8=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
Expand Down
2 changes: 1 addition & 1 deletion internal/docs/readme.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func renderReadme(fileName, packageRoot, templatePath string, linksMap linkMap)
t := template.New(fileName)
t, err := t.Funcs(template.FuncMap{
"event": func(dataStreamName string) (string, error) {
return renderSampleEvent(packageRoot, dataStreamName)
return renderSampleEvents(packageRoot, dataStreamName)
},
"fields": func(args ...string) (string, error) {
if len(args) > 0 {
Expand Down
2 changes: 1 addition & 1 deletion internal/docs/readme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ func createSampleEventFile(packageRoot, dataStreamName, contents string) error {
return err
}

sampleEventFile := filepath.Join(dataStreamFolder, sampleEventFile)
sampleEventFile := filepath.Join(dataStreamFolder, "sample_event.json")
if err := os.WriteFile(sampleEventFile, []byte(contents), 0644); err != nil {
return err
}
Expand Down
53 changes: 41 additions & 12 deletions internal/docs/sample_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,64 @@ package docs

import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"

"github.com/elastic/elastic-package/internal/formatter"
)

const sampleEventFile = "sample_event.json"
const sampleEventFilePattern = "sample_event*.json"

func renderSampleEvent(packageRoot, dataStreamName string) (string, error) {
eventPath := filepath.Join(packageRoot, "data_stream", dataStreamName, sampleEventFile)
func renderSampleEvents(packageRoot, dataStreamName string) (string, error) {
eventPaths, err := filepath.Glob(filepath.Join(packageRoot, "data_stream", dataStreamName, sampleEventFilePattern))
if err != nil {
return "", fmt.Errorf("failed to look for sample event files: %w", err)
}
if len(eventPaths) == 0 {
return "", fmt.Errorf("could not find any sample event for data stream %s", dataStreamName)
}

var builder strings.Builder
if len(eventPaths) == 1 {
fmt.Fprintf(&builder, "An example event for `%s` looks as following:\n\n",
stripDataStreamFolderSuffix(dataStreamName))
} else {
fmt.Fprintf(&builder, "Example events for `%s` look as following:\n\n",
stripDataStreamFolderSuffix(dataStreamName))
}

sort.Strings(eventPaths)
for i, eventPath := range eventPaths {
if i > 0 {
fmt.Fprintln(&builder)
}
err := renderSampleEvent(&builder, eventPath)
if err != nil {
return "", err
}
}

return builder.String(), nil
}

func renderSampleEvent(w io.Writer, eventPath string) error {
body, err := os.ReadFile(eventPath)
if err != nil {
return "", fmt.Errorf("reading sample event file failed (path: %s): %w", eventPath, err)
return fmt.Errorf("reading sample event file failed (path: %s): %w", eventPath, err)
}

formatted, _, err := formatter.JSONFormatter(body)
if err != nil {
return "", fmt.Errorf("formatting sample event file failed (path: %s): %w", eventPath, err)
return fmt.Errorf("formatting sample event file failed (path: %s): %w", eventPath, err)
}

var builder strings.Builder
builder.WriteString(fmt.Sprintf("An example event for `%s` looks as following:\n\n",
stripDataStreamFolderSuffix(dataStreamName)))
builder.WriteString("```json\n")
builder.Write(formatted)
builder.WriteString("\n```")
return builder.String(), nil
fmt.Fprintln(w, "```json")
fmt.Fprintln(w, string(formatted))
fmt.Fprint(w, "```")
return nil
}

func stripDataStreamFolderSuffix(dataStreamName string) string {
Expand Down
13 changes: 5 additions & 8 deletions internal/formatter/json_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,19 @@
package formatter

import (
"bytes"
"encoding/json"
"fmt"
)

// JSONFormatter function is responsible for formatting the given JSON input.
// The function is exposed, so it can be used by other internal packages, e.g. to format sample events in docs.
func JSONFormatter(content []byte) ([]byte, bool, error) {
var rawMessage json.RawMessage
err := json.Unmarshal(content, &rawMessage)
var formatted bytes.Buffer
err := json.Indent(&formatted, content, "", " ")
if err != nil {
return nil, false, fmt.Errorf("unmarshalling JSON file failed: %w", err)
return nil, false, fmt.Errorf("indenting JSON failed: %w", err)
}

formatted, err := json.MarshalIndent(&rawMessage, "", " ")
if err != nil {
return nil, false, fmt.Errorf("marshalling JSON raw message failed: %w", err)
}
return formatted, string(content) == string(formatted), nil
return formatted.Bytes(), string(content) == formatted.String(), nil
}
61 changes: 31 additions & 30 deletions internal/testrunner/runners/static/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package static

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand All @@ -16,7 +15,7 @@ import (
"github.com/elastic/elastic-package/internal/testrunner"
)

const sampleEventJSON = "sample_event.json"
const sampleEventFilePattern = "sample_event*.json"

type runner struct {
options testrunner.TestOptions
Expand Down Expand Up @@ -75,18 +74,18 @@ func (r runner) run() ([]testrunner.TestResult, error) {

func (r runner) verifySampleEvent(pkgManifest *packages.PackageManifest) []testrunner.TestResult {
resultComposer := testrunner.NewResultComposer(testrunner.TestResult{
Name: "Verify " + sampleEventJSON,
Name: "Verify sample events",
TestType: TestType,
Package: r.options.TestFolder.Package,
DataStream: r.options.TestFolder.DataStream,
})

sampleEventPath, found, err := r.getSampleEventPath()
sampleEventPaths, err := r.findSampleEventPaths()
if err != nil {
results, _ := resultComposer.WithError(err)
return results
}
if !found {
if len(sampleEventPaths) == 0 {
// Nothing to do.
return []testrunner.TestResult{}
}
Expand All @@ -96,7 +95,7 @@ func (r runner) verifySampleEvent(pkgManifest *packages.PackageManifest) []testr
results, _ := resultComposer.WithError(err)
return results
}
fieldsValidator, err := fields.CreateValidatorForDirectory(filepath.Dir(sampleEventPath),
fieldsValidator, err := fields.CreateValidatorForDirectory(filepath.Dir(sampleEventPaths[0]),
fields.WithSpecVersion(pkgManifest.SpecVersion),
fields.WithDefaultNumericConversion(),
fields.WithExpectedDatasets(expectedDatasets),
Expand All @@ -107,44 +106,46 @@ func (r runner) verifySampleEvent(pkgManifest *packages.PackageManifest) []testr
return results
}

content, err := os.ReadFile(sampleEventPath)
if err != nil {
results, _ := resultComposer.WithError(fmt.Errorf("can't read file: %w", err))
return results
}

multiErr := fieldsValidator.ValidateDocumentBody(content)
if len(multiErr) > 0 {
results, _ := resultComposer.WithError(testrunner.ErrTestCaseFailed{
Reason: "one or more errors found in document",
Details: multiErr.Error(),
})
return results
for _, sampleEventPath := range sampleEventPaths {
logger.Debugf("Validating fields in sample event %s", sampleEventPath)
content, err := os.ReadFile(sampleEventPath)
if err != nil {
results, _ := resultComposer.WithError(fmt.Errorf("can't read file: %w", err))
return results
}

multiErr := fieldsValidator.ValidateDocumentBody(content)
if len(multiErr) > 0 {
results, _ := resultComposer.WithError(testrunner.ErrTestCaseFailed{
Reason: fmt.Sprintf("one or more errors found in sample document \"%s\"", sampleEventPath),
Details: multiErr.Error(),
})
return results
}
}

results, _ := resultComposer.WithSuccess()
return results
}

func (r runner) getSampleEventPath() (string, bool, error) {
var sampleEventPath string
func (r runner) findSampleEventPaths() ([]string, error) {
var pattern string
if r.options.TestFolder.DataStream != "" {
sampleEventPath = filepath.Join(
pattern = filepath.Join(
r.options.PackageRootPath,
"data_stream",
r.options.TestFolder.DataStream,
sampleEventJSON)
sampleEventFilePattern)
} else {
sampleEventPath = filepath.Join(r.options.PackageRootPath, sampleEventJSON)
}
_, err := os.Stat(sampleEventPath)
if errors.Is(err, os.ErrNotExist) {
return "", false, nil
pattern = filepath.Join(r.options.PackageRootPath, sampleEventFilePattern)
}

files, err := filepath.Glob(pattern)
if err != nil {
return "", false, fmt.Errorf("stat file failed: %w", err)
return nil, fmt.Errorf("glob failed (pattern: %s): %w", pattern, err)
}
return sampleEventPath, true, nil

return files, nil
}

func (r runner) getExpectedDatasets(pkgManifest *packages.PackageManifest) ([]string, error) {
Expand Down
36 changes: 30 additions & 6 deletions internal/testrunner/runners/system/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ func (r *runner) runTest(config *testConfig, ctxt servicedeployer.ServiceContext
}

// Write sample events file from first doc, if requested
if err := r.generateTestResult(docs); err != nil {
if err := r.generateTestResult(config, docs); err != nil {
return result.WithError(err)
}

Expand Down Expand Up @@ -1183,13 +1183,18 @@ func filterAgents(allAgents []kibana.Agent, ctx servicedeployer.ServiceContext)
return filtered
}

func writeSampleEvent(path string, doc common.MapStr) error {
func writeSampleEvent(path string, doc common.MapStr, name string) error {
body, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return fmt.Errorf("marshalling sample event failed: %w", err)
}

err = os.WriteFile(filepath.Join(path, "sample_event.json"), body, 0644)
sampleName := "sample_event.json"
if name != "" {
sampleName = "sample_event_" + name + ".json"
}

err = os.WriteFile(filepath.Join(path, sampleName), body, 0644)
if err != nil {
return fmt.Errorf("writing sample event failed: %w", err)
}
Expand Down Expand Up @@ -1249,7 +1254,7 @@ func (r *runner) selectVariants(variantsFile *servicedeployer.VariantsFile) []st
return variantNames
}

func (r *runner) generateTestResult(docs []common.MapStr) error {
func (r *runner) generateTestResult(config *testConfig, docs []common.MapStr) error {
if !r.options.GenerateTestResult {
return nil
}
Expand All @@ -1259,8 +1264,27 @@ func (r *runner) generateTestResult(docs []common.MapStr) error {
rootPath = filepath.Join(rootPath, "data_stream", ds)
}

if err := writeSampleEvent(rootPath, docs[0]); err != nil {
return fmt.Errorf("failed to write sample event file: %w", err)
if len(config.Samples) == 0 {
if err := writeSampleEvent(rootPath, docs[0], ""); err != nil {
return fmt.Errorf("failed to write sample event file: %w", err)
}
return nil
}

for _, sample := range config.Samples {
found := false
for _, doc := range docs {
if sample.Matches(doc) {
if err := writeSampleEvent(rootPath, doc, sample.Name); err != nil {
return fmt.Errorf("failed to write sample event file: %w", err)
}
found = true
break
}
}
if !found {
return fmt.Errorf("could not find sample for %q", sample.Name)
}
}

return nil
Expand Down
Loading