Skip to content
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
78b6c7d
Add validation for data stream template paths
teresaromero Sep 26, 2025
6b5db09
Add new testdata packages for stream template validation
teresaromero Sep 26, 2025
78ebe46
Add validation for stream templates and corresponding tests
teresaromero Sep 26, 2025
f09e0d6
Add validation for input and integration policy template paths with c…
teresaromero Sep 26, 2025
84cf7b1
Fix policy template validation to support nested inputs and improve e…
teresaromero Sep 26, 2025
45f462b
Add testdata for input and integration policy templates
teresaromero Sep 26, 2025
cd6c408
Add tests for input and integration policy template validation, inclu…
teresaromero Sep 26, 2025
bc144d4
Format imports with goimports
teresaromero Sep 26, 2025
edbba40
Move validation test from spec to validator. Move testdata to test/pa…
teresaromero Sep 26, 2025
5045d28
fix test maifest and spec example to align to validation path
teresaromero Sep 29, 2025
423793f
Refactor validateDataStreamManifestTemplates to simplify parameters b…
teresaromero Sep 29, 2025
fb191e2
Improve error messaging in template path validation by using %w for w…
teresaromero Sep 29, 2025
526c665
Update error messages in template path validation tests for clarity a…
teresaromero Sep 29, 2025
a78f6c2
Add validation for template_path in policy templates and data streams
teresaromero Sep 29, 2025
4fa3db6
Add validation for `template_path` in policy templates and data strea…
teresaromero Sep 30, 2025
364d708
Refactor template path handling in validation functions to use filepa…
teresaromero Sep 30, 2025
6e35950
Update tests to use filepath
teresaromero Sep 30, 2025
0d3e22c
filepath join at tests
teresaromero Sep 30, 2025
9dca430
Refactor tests to use os and filepath for directory and file handling…
teresaromero Sep 30, 2025
704aef2
Reorder import statements
teresaromero Sep 30, 2025
801b990
Refactor error handling in template validation functions to use prede…
teresaromero Oct 1, 2025
741ffb0
Merge branch 'main' into 703-stream-yml-hbs-exists
teresaromero Oct 1, 2025
8f47f3c
remove changelog template_path quotes
teresaromero Oct 1, 2025
9d3e1dd
Comment out validation rules and test cases for stream and input poli…
teresaromero Oct 1, 2025
316dbd3
Refactor path handling in validation functions to use path.Join inste…
teresaromero Oct 1, 2025
13f5ae8
Uncomment validation rules and test cases for stream and input policy…
teresaromero Oct 1, 2025
cfa7087
Fix changelog entry order
teresaromero Oct 1, 2025
19bf9da
Add validation for default template paths in data stream tests
teresaromero Oct 3, 2025
731a589
Rename validation function for agent input template paths and update …
teresaromero Oct 3, 2025
807f407
Add stream template files for data stream agent validation tests
teresaromero Oct 3, 2025
b2be7dd
Refactor error handling in validateAgentInputTemplatePath to differen…
teresaromero Oct 3, 2025
0882440
Change specs with required fields and json patch
teresaromero Oct 3, 2025
a6379bf
Update validation rules and manifest files for version 3.6.0 complian…
teresaromero Oct 3, 2025
bfe466b
revert changes on old test packages
teresaromero Oct 3, 2025
53ccaf3
recover deleted file
teresaromero Oct 3, 2025
f1369eb
Revert "revert changes on old test packages"
teresaromero Oct 3, 2025
d0dd5d8
revert version limit
teresaromero Oct 6, 2025
6ee279c
fix test cases
teresaromero Oct 6, 2025
4da74b3
fix testdata with template_paths
teresaromero Oct 7, 2025
5d1ac9c
Revert "fix testdata with template_paths"
teresaromero Oct 7, 2025
616761c
Revert "fix test cases"
teresaromero Oct 7, 2025
7b474a7
revert required field at integrations, fix required validation for in…
teresaromero Oct 7, 2025
818eff5
change streams template_path validation, use walkdir with endsWith logic
teresaromero Oct 8, 2025
86fec86
Merge branch 'main' into 703-stream-yml-hbs-exists
teresaromero Oct 14, 2025
84d2161
changelog move template_path validation entry to 3.5.1-next
teresaromero Oct 15, 2025
a84f9c7
Rename defaultTemplatePath to defaultStreamTemplatePath for clarity
teresaromero Oct 15, 2025
fd34e79
Change fs.SkipDir to fs.SkipAll to stop directory traversal once a ma…
teresaromero Oct 15, 2025
11efa5d
Fix error handling in validateDataStreamManifestTemplates for directo…
teresaromero Oct 15, 2025
8ec5de0
Update link in comment to point to the correct commit in Kibana repos…
teresaromero Oct 15, 2025
a3b56e5
Refactor policy template validation: consolidate ValidateStreamTempla…
teresaromero Oct 16, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import (
"errors"
"io/fs"
"path"
"strings"

"gopkg.in/yaml.v3"

"github.com/elastic/package-spec/v3/code/go/internal/fspath"
"github.com/elastic/package-spec/v3/code/go/pkg/specerrors"
)

const (
defaultTemplatePath = "stream.yml.hbs"
)

var (
errFailedToReadManifest = errors.New("failed to read manifest")
errFailedToParseManifest = errors.New("failed to parse manifest")
Expand Down Expand Up @@ -60,17 +65,38 @@ func validateDataStreamManifestTemplates(fsys fspath.FS, dataStreamName string)
}

for _, stream := range manifest.Streams {
streamPath := stream.TemplatePath
if stream.TemplatePath == "" {
continue // template_path is optional
// When no template_path is specified, it defaults to "stream.yml.hbs"
streamPath = defaultTemplatePath
}

// Check if template file exists
templatePath := path.Join("data_stream", dataStreamName, "agent", "stream", stream.TemplatePath)
_, err := fs.Stat(fsys, templatePath)
if err != nil {
// Walk through the "data_stream/<dataStreamName>/agent/stream" directory
// This mirrors the logic in fleet where the assets are filtered based on the template_path
// https://github.com/elastic/kibana/blob/main/x-pack/platform/plugins/shared/fleet/server/services/package_policy.ts#L3317
streamDir := path.Join("data_stream", dataStreamName, "agent", "stream")
found := false
fs.WalkDir(fsys, streamDir, func(filePath string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if !d.IsDir() && path.Base(filePath) != "" && strings.HasSuffix(filePath, streamPath) {
found = true
return fs.SkipDir // Stop walking once found
}
return nil
})
if !found {
if stream.TemplatePath == "" {
errs = append(errs, specerrors.NewStructuredErrorf(
"file \"%s\" is invalid: stream \"%s\" is missing template file \"%s\": %w",
fsys.Path(manifestPath), stream.Input, streamPath, errTemplateNotFound))
continue
}
errs = append(errs, specerrors.NewStructuredErrorf(
"file \"%s\" is invalid: stream \"%s\" references template_path \"%s\": %w",
fsys.Path(manifestPath), stream.Input, stream.TemplatePath, errTemplateNotFound))
fsys.Path(manifestPath), stream.Input, streamPath, errTemplateNotFound))
continue
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,45 @@ streams:
err = os.WriteFile(filepath.Join(d, "data_stream", "test", "agent", "stream", "udp.yml.hbs"), []byte("# UDP template"), 0o644)
require.NoError(t, err)

errs := ValidateStreamTemplates(fspath.DirFS(d))
require.Empty(t, errs, "expected no validation errors")
})

t.Run("valid_data_stream_with_default_templates", func(t *testing.T) {
d := t.TempDir()
err := os.MkdirAll(filepath.Join(d, "data_stream", "test", "agent", "stream"), 0o755)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(d, "data_stream", "test", "manifest.yml"), []byte(`
streams:
- input: udp
title: Test UDP
description: Test UDP stream
`), 0o644)
require.NoError(t, err)

err = os.WriteFile(filepath.Join(d, "data_stream", "test", "agent", "stream", "stream.yml.hbs"), []byte("# UDP template"), 0o644)
require.NoError(t, err)

errs := ValidateStreamTemplates(fspath.DirFS(d))
require.Empty(t, errs, "expected no validation errors")

})

t.Run("valid_data_stream_with_default_templates_endsWith_stream", func(t *testing.T) {
d := t.TempDir()
err := os.MkdirAll(filepath.Join(d, "data_stream", "test", "agent", "stream"), 0o755)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(d, "data_stream", "test", "manifest.yml"), []byte(`
streams:
- input: udp
title: Test UDP
description: Test UDP stream
`), 0o644)
require.NoError(t, err)

err = os.WriteFile(filepath.Join(d, "data_stream", "test", "agent", "stream", "filestream.yml.hbs"), []byte("# UDP template"), 0o644)
require.NoError(t, err)

errs := ValidateStreamTemplates(fspath.DirFS(d))
require.Empty(t, errs, "expected no validation errors")

Expand All @@ -46,7 +85,7 @@ streams:
errs := ValidateStreamTemplates(fspath.DirFS(d))
require.NotEmpty(t, errs, "expected validation errors")
assert.Len(t, errs, 1)
assert.ErrorAs(t, errs[0], &errFailedToReadManifest)
assert.ErrorIs(t, errs[0], errFailedToReadManifest)
})

t.Run("err_parse_manifest", func(t *testing.T) {
Expand All @@ -68,7 +107,7 @@ streams:
errs := ValidateStreamTemplates(fspath.DirFS(d))
require.NotEmpty(t, errs, "expected validation errors")
assert.Len(t, errs, 1)
assert.ErrorAs(t, errs[0], &errFailedToParseManifest)
assert.ErrorIs(t, errs[0], errFailedToParseManifest)

})

Expand All @@ -88,7 +127,27 @@ streams:
errs := ValidateStreamTemplates(fspath.DirFS(d))
require.NotEmpty(t, errs, "expected validation errors")
assert.Len(t, errs, 1)
assert.ErrorAs(t, errs[0], &errTemplateNotFound)
assert.ErrorIs(t, errs[0], errTemplateNotFound)
assert.ErrorContains(t, errs[0], "stream \"udp\" references template_path \"missing.yml.hbs\": template file not found")
})

t.Run("err_missing_default_template_file", func(t *testing.T) {
d := t.TempDir()
err := os.MkdirAll(filepath.Join(d, "data_stream", "test", "agent", "stream"), 0o755)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(d, "data_stream", "test", "manifest.yml"), []byte(`
streams:
- input: udp
title: Test UDP
description: Test UDP stream
`), 0o644)
require.NoError(t, err)

errs := ValidateStreamTemplates(fspath.DirFS(d))
require.NotEmpty(t, errs, "expected validation errors")
assert.Len(t, errs, 1)
assert.ErrorIs(t, errs[0], errTemplateNotFound)
assert.ErrorContains(t, errs[0], "stream \"udp\" is missing template file \"stream.yml.hbs\": template file not found")
})

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
package semantic

import (
"errors"
"fmt"
"io/fs"
"os"
"path"

"gopkg.in/yaml.v3"
Expand All @@ -30,10 +33,10 @@ func ValidateInputPolicyTemplates(fsys fspath.FS) specerrors.ValidationErrors {

PolicyTemplates []struct {
Name string `yaml:"name"`
TemplatePath string `yaml:"template_path"` // optional, input type packages
TemplatePath string `yaml:"template_path"` // input type packages require this field
Inputs []struct {
Title string `yaml:"title"`
TemplatePath string `yaml:"template_path"` // optional, integration type packages
TemplatePath string `yaml:"template_path"` // optional for integration packages
} `yaml:"inputs"`
} `yaml:"policy_templates"`
}
Expand All @@ -50,7 +53,7 @@ func ValidateInputPolicyTemplates(fsys fspath.FS) specerrors.ValidationErrors {
if input.TemplatePath == "" {
continue // template_path is optional
}
err := validateTemplatePath(fsys, input.TemplatePath)
err := validateAgentInputTemplatePath(fsys, input.TemplatePath)
if err != nil {
errs = append(errs, specerrors.NewStructuredErrorf(
"file \"%s\" is invalid: policy template \"%s\" references template_path \"%s\": %w",
Expand All @@ -60,9 +63,12 @@ func ValidateInputPolicyTemplates(fsys fspath.FS) specerrors.ValidationErrors {

case "input":
if policyTemplate.TemplatePath == "" {
continue // template_path is optional
errs = append(errs, specerrors.NewStructuredErrorf(
"file \"%s\" is invalid: policy template \"%s\" is missing required field \"template_path\"",
fsys.Path(manifestPath), policyTemplate.Name))
continue
}
err := validateTemplatePath(fsys, policyTemplate.TemplatePath)
err := validateAgentInputTemplatePath(fsys, policyTemplate.TemplatePath)
if err != nil {
errs = append(errs, specerrors.NewStructuredErrorf(
"file \"%s\" is invalid: policy template \"%s\" references template_path \"%s\": %w",
Expand All @@ -74,11 +80,14 @@ func ValidateInputPolicyTemplates(fsys fspath.FS) specerrors.ValidationErrors {
return errs
}

func validateTemplatePath(fsys fspath.FS, tmplPath string) error {
func validateAgentInputTemplatePath(fsys fspath.FS, tmplPath string) error {
templatePath := path.Join("agent", "input", tmplPath)
_, err := fs.Stat(fsys, templatePath)
if err != nil {
return errTemplateNotFound
if errors.Is(err, os.ErrNotExist) {
return errTemplateNotFound
}
return fmt.Errorf("failed to stat template file %s: %w", fsys.Path(templatePath), err)
}

return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@ policy_templates:

})

t.Run("input_manifest_with_policy_template_missing_template_path", func(t *testing.T) {
d := t.TempDir()

err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(d, "manifest.yml"), []byte(`
type: input
policy_templates:
- name: udp
`), 0o644)
require.NoError(t, err)

errs := ValidateInputPolicyTemplates(fspath.DirFS(d))
require.NotEmpty(t, errs, "expected no validation errors")

assert.Len(t, errs, 1)
assert.Contains(t, errs[0].Error(), "is missing required field \"template_path\"")
})

t.Run("input_manifest_with_policy_template_missing_template_file", func(t *testing.T) {
d := t.TempDir()

Expand All @@ -53,7 +72,7 @@ policy_templates:
errs := ValidateInputPolicyTemplates(fspath.DirFS(d))
require.NotEmpty(t, errs, "expected validation errors")
assert.Len(t, errs, 1)
assert.ErrorAs(t, errs[0], &errTemplateNotFound)
assert.ErrorIs(t, errs[0], errTemplateNotFound)
})
t.Run("integration_manifest_with_policy_template_success", func(t *testing.T) {
d := t.TempDir()
Expand Down Expand Up @@ -92,6 +111,6 @@ policy_templates:
errs := ValidateInputPolicyTemplates(fspath.DirFS(d))
require.NotEmpty(t, errs, "expected validation errors")
assert.Len(t, errs, 1)
assert.ErrorAs(t, errs[0], &errTemplateNotFound)
assert.ErrorIs(t, errs[0], errTemplateNotFound)
})
}
1 change: 1 addition & 0 deletions code/go/pkg/validator/limits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func (fs *mockFS) Good() *mockFS {
newMockFile("_dev/deploy/docker/docker-compose.yml").WithContent("version: 2.3"),
newMockFile("data_stream/foo/manifest.yml").WithContent(datastreamManifestYml),
newMockFile("data_stream/foo/fields/base-fields.yml").WithContent(fieldsYml),
newMockFile("data_stream/foo/agent/stream/stream.yml.hbs").WithContent("# Stream template"),
)
}

Expand Down
6 changes: 3 additions & 3 deletions spec/changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
##
- version: 3.6.0-next
changes:
- description: Add validation for template_path in policy templates and data streams.
type: enhancement
link: https://github.com/elastic/package-spec/pull/986
# Pending on https://github.com/elastic/kibana/pull/186974
- description: Add support for `slo` assets.
type: enhancement
Expand All @@ -15,6 +12,9 @@
- description: Add support for semantic_text field definition.
type: enhancement
link: https://github.com/elastic/package-spec/pull/807
- description: Add validation for template_path in policy templates and data streams.
type: enhancement
link: https://github.com/elastic/package-spec/pull/986
- version: 3.5.1-next
changes:
- description: Input packages don't require to define fields.
Expand Down
1 change: 1 addition & 0 deletions spec/input/manifest.spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ spec:
- description
- type
- input
- template_path
icons:
$ref: "../integration/manifest.spec.yml#/definitions/icons"
screenshots:
Expand Down
1 change: 1 addition & 0 deletions spec/integration/data_stream/manifest.spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ spec:
template_path:
description: "Path to Elasticsearch index template for stream."
type: string
default: "stream.yml.hbs"
required_vars:
$ref: "#/definitions/required_vars"
vars:
Expand Down
1 change: 1 addition & 0 deletions test/packages/bad_duplicated_fields_input/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ policy_templates:
type: logs
input: log_file
description: Collect sample logs
template_path: input.yml.hbs
vars:
- name: paths
required: true
Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
4 changes: 4 additions & 0 deletions test/packages/good_v3/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ policy_templates:
- http://127.0.0.1
hide_in_deployment_modes:
- agentless
template_path: apache_metrics.yml.hbs
- type: httpjson
title: Collect data via HTTP JSON API
description: Collecting data from HTTP JSON API (default only)
Expand All @@ -110,6 +111,7 @@ policy_templates:
min_duration: 10s
max_duration: 4h3m2s1ms
default: 1m
template_path: httpjson.yml.hbs
- name: apache-agentless
title: Apache logs and metrics in agentless
description: Collect logs and metrics from Apache instances in agentless
Expand Down Expand Up @@ -143,6 +145,7 @@ policy_templates:
show_user: true
default:
- http://127.0.0.1
template_path: apache_metrics.yml.hbs
- type: aws/s3
title: Collect S3 logs (agentless only)
description: Collecting logs from AWS S3 in agentless mode
Expand All @@ -154,6 +157,7 @@ policy_templates:
title: S3 Bucket Name
show_user: true
required: true
template_path: aws_s3.yml.hbs
owner:
github: elastic/foobar
type: elastic
Expand Down
2 changes: 1 addition & 1 deletion test/packages/input_policy_template_invalid/manifest.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
format_version: 3.4.1
format_version: 3.6.0
name: input_policy_template_valid
title: "New Package"
version: 0.0.1
Expand Down
2 changes: 1 addition & 1 deletion test/packages/input_policy_template_valid/manifest.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
format_version: 3.4.1
format_version: 3.6.0
name: input_policy_template_valid
title: "New Package"
version: 0.0.1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
format_version: 3.4.1
format_version: 3.6.0
name: integration_policy_template_valid
title: "New Package"
version: 0.0.1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
format_version: 3.4.1
format_version: 3.6.0
name: integration_policy_template_valid
title: "New Package"
version: 0.0.1
Expand Down
1 change: 1 addition & 0 deletions test/packages/missing_required_fields_input/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ policy_templates:
input: log_file
title: Sample logs
description: Collect sample logs
template_path: input.yml.hbs
vars:
- name: paths
required: true
Expand Down
Empty file.
3 changes: 2 additions & 1 deletion test/packages/stream_templates_invalid/manifest.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
format_version: 3.4.1
format_version: 3.6.0
name: stream_templates_valid
title: "Stream Templates Valid Test"
version: 0.0.1
Expand Down Expand Up @@ -31,6 +31,7 @@ policy_templates:
- type: logfile
title: Collect sample logs from instances
description: Collecting sample logs
template_path: template.yml.hbs
owner:
github: elastic/integrations
type: elastic
Empty file.
Loading