Skip to content
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

Validate the proper value of event.dataset #968

Merged
merged 4 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
27 changes: 27 additions & 0 deletions docs/howto/update_major_package_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,30 @@ some of the expected values.

For example if a document contains `event.category: web`, the value of
`event.type` must be `access`, `error` or `info` according to ECS 8.4.

### field "event.dataset" should have value ..., it has ...

The field `event.dataset` should contain the name of the package and the name of
the data stream that generates it, separated by a dot. For example for documents
of the "access" data stream of the Apache module, it should be `apache.access`.

To fix this, review what is generating the value of this field. Depending on
your case, you may apply one of the following solutions.

Fix the value in the field definition using a `constant_keyword`:
```
- name: event.dataset
type: constant_keyword
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not possible now. We don't allow to override the type of an imported value. But I think it would make sense to support the change from keyword to constant_keyword. I will open a PR to support it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in #969.

external: ecs
value: "apache.access"
```

Explicitly set the value in the pipeline:
```
- set:
field: event.dataset
value: "apache.access"
```

Changing the value of `event.dataset` can be considered a breaking change, take
this into account in your package when adding the changelog entry.
29 changes: 28 additions & 1 deletion internal/fields/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type Validator struct {
// SpecVersion contains the version of the spec used by the package.
specVersion semver.Version

// expectedDataset contains the value expected for dataset fields.
expectedDataset string

defaultNumericConversion bool
numericKeywordFields map[string]struct{}

Expand Down Expand Up @@ -98,6 +101,14 @@ func WithEnabledAllowedIPCheck() ValidatorOption {
}
}

// WithExpectedDataset configures the validator to check if the dataset fields have the expected values.
func WithExpectedDataset(dataset string) ValidatorOption {
return func(v *Validator) error {
v.expectedDataset = dataset
return nil
}
}

// CreateValidatorForDirectory function creates a validator for the directory.
func CreateValidatorForDirectory(fieldsParentDir string, opts ...ValidatorOption) (v *Validator, err error) {
v = new(Validator)
Expand Down Expand Up @@ -202,13 +213,29 @@ func (v *Validator) ValidateDocumentBody(body json.RawMessage) multierror.Error

// ValidateDocumentMap validates the provided document as common.MapStr.
func (v *Validator) ValidateDocumentMap(body common.MapStr) multierror.Error {
errs := v.validateMapElement("", body, body)
errs := v.validateDocumentValues(body)
errs = append(errs, v.validateMapElement("", body, body)...)
if len(errs) == 0 {
return nil
}
return errs
}

func (v *Validator) validateDocumentValues(body common.MapStr) multierror.Error {
var errs multierror.Error
if !v.specVersion.LessThan(semver2_0_0) && v.expectedDataset != "" {
const datasetField = "event.dataset"
value, _ := body.GetValue(datasetField)
str, ok := value.(string)
if !ok || str != v.expectedDataset {
err := errors.Errorf("field %q should have value %q, it has \"%v\"",
datasetField, v.expectedDataset, value)
errs = append(errs, err)
}
}
return errs
}

func (v *Validator) validateMapElement(root string, elem common.MapStr, doc common.MapStr) multierror.Error {
var errs multierror.Error
for name, val := range elem {
Expand Down
55 changes: 55 additions & 0 deletions internal/fields/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,61 @@ func TestValidate_ExpectedEventType(t *testing.T) {
}
}

func TestValidate_ExpectedDataset(t *testing.T) {
validator, err := CreateValidatorForDirectory("testdata",
WithSpecVersion("2.0.0"),
WithExpectedDataset("apache.status"),
)
require.NoError(t, err)
require.NotNil(t, validator)

cases := []struct {
title string
doc common.MapStr
valid bool
}{
{
title: "valid dataset",
doc: common.MapStr{
"event.dataset": "apache.status",
},
valid: true,
},
{
title: "empty dataset",
doc: common.MapStr{
"event.dataset": "",
},
valid: false,
},
{
title: "absent dataset",
doc: common.MapStr{},
valid: false,
},
{
title: "wrong dataset",
doc: common.MapStr{
"event.dataset": "httpd.status",
},
valid: false,
},
}

for _, c := range cases {
t.Run(c.title, func(t *testing.T) {
errs := validator.ValidateDocumentMap(c.doc)
if c.valid {
assert.Empty(t, errs)
} else {
if assert.Len(t, errs, 1) {
assert.Contains(t, errs[0].Error(), `field "event.dataset" should have value`)
}
}
})
}
}

func Test_parseElementValue(t *testing.T) {
for _, test := range []struct {
key string
Expand Down
5 changes: 4 additions & 1 deletion internal/testrunner/runners/static/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,12 @@ func (r runner) verifySampleEvent(pkgManifest *packages.PackageManifest) []testr
return results
}

expectedDataset := pkgManifest.Name + "." + r.options.TestFolder.DataStream
fieldsValidator, err := fields.CreateValidatorForDirectory(dataStreamPath,
fields.WithSpecVersion(pkgManifest.SpecVersion),
fields.WithDefaultNumericConversion())
fields.WithDefaultNumericConversion(),
fields.WithExpectedDataset(expectedDataset),
)
if err != nil {
results, _ := resultComposer.WithError(errors.Wrap(err, "creating fields validator for data stream failed"))
return results
Expand Down
5 changes: 4 additions & 1 deletion internal/testrunner/runners/system/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,9 +447,12 @@ func (r *runner) runTest(config *testConfig, ctxt servicedeployer.ServiceContext
}

// Validate fields in docs
expectedDataset := pkgManifest.Name + "." + r.options.TestFolder.DataStream
fieldsValidator, err := fields.CreateValidatorForDirectory(serviceOptions.DataStreamRootPath,
fields.WithSpecVersion(pkgManifest.SpecVersion),
fields.WithNumericKeywordFields(config.NumericKeywordFields))
fields.WithNumericKeywordFields(config.NumericKeywordFields),
fields.WithExpectedDataset(expectedDataset),
)
if err != nil {
return result.WithError(errors.Wrapf(err, "creating fields validator for data stream failed (path: %s)", serviceOptions.DataStreamRootPath))
}
Expand Down