Skip to content

Commit d6a02c1

Browse files
authored
Add validation for template_path at input packages (#1000)
Adding new rule for validating the `template_path` field under the `policy_template` at the package manifest
1 parent feb65dc commit d6a02c1

33 files changed

+762
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package semantic
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"io/fs"
11+
"os"
12+
"path"
13+
14+
"gopkg.in/yaml.v3"
15+
16+
"github.com/elastic/package-spec/v3/code/go/internal/fspath"
17+
"github.com/elastic/package-spec/v3/code/go/pkg/specerrors"
18+
)
19+
20+
const (
21+
inputPackageType string = "input"
22+
)
23+
24+
var (
25+
errRequiredTemplatePath = errors.New("template_path is required for input type packages")
26+
errFailedToReadManifest = errors.New("failed to read manifest")
27+
errFailedToParseManifest = errors.New("failed to parse manifest")
28+
errTemplateNotFound = errors.New("template file not found")
29+
errInvalidPackageType = errors.New("invalid package type")
30+
)
31+
32+
type inputPolicyTemplate struct {
33+
Name string `yaml:"name"`
34+
TemplatePath string `yaml:"template_path"` // input type packages require this field
35+
}
36+
37+
type inputPackageManifest struct { // package manifest
38+
Type string `yaml:"type"`
39+
PolicyTemplates []inputPolicyTemplate `yaml:"policy_templates"`
40+
}
41+
42+
// ValidateInputPackagesPolicyTemplates validates the policy template entries of an input package
43+
func ValidateInputPackagesPolicyTemplates(fsys fspath.FS) specerrors.ValidationErrors {
44+
var errs specerrors.ValidationErrors
45+
46+
manifestPath := "manifest.yml"
47+
data, err := fs.ReadFile(fsys, manifestPath)
48+
if err != nil {
49+
return specerrors.ValidationErrors{
50+
specerrors.NewStructuredErrorf("file \"%s\" is invalid: %ww", fsys.Path(manifestPath), errFailedToReadManifest)}
51+
}
52+
53+
var manifest inputPackageManifest
54+
err = yaml.Unmarshal(data, &manifest)
55+
if err != nil {
56+
return specerrors.ValidationErrors{
57+
specerrors.NewStructuredErrorf("file \"%s\" is invalid: %w", fsys.Path(manifestPath), errFailedToParseManifest)}
58+
}
59+
60+
if manifest.Type != inputPackageType {
61+
return specerrors.ValidationErrors{
62+
specerrors.NewStructuredErrorf("file \"%s\" is invalid: expected package type \"%s\", got \"%s\": %w",
63+
fsys.Path(manifestPath), inputPackageType, manifest.Type, errInvalidPackageType)}
64+
}
65+
66+
for _, policyTemplate := range manifest.PolicyTemplates {
67+
err := validateInputPackagePolicyTemplate(fsys, policyTemplate)
68+
if err != nil {
69+
errs = append(errs, specerrors.NewStructuredErrorf(
70+
"file \"%s\" is invalid: policy template \"%s\" references template_path \"%s\": %w",
71+
fsys.Path(manifestPath), policyTemplate.Name, policyTemplate.TemplatePath, err))
72+
}
73+
}
74+
75+
return errs
76+
}
77+
78+
// validateInputPackagePolicyTemplate validates the template_path at the policy template level for input type packages
79+
// if the template_path is empty, it returns an error as this field is required for input type packages
80+
func validateInputPackagePolicyTemplate(fsys fspath.FS, policyTemplate inputPolicyTemplate) error {
81+
if policyTemplate.TemplatePath == "" {
82+
return errRequiredTemplatePath
83+
}
84+
return validateAgentInputTemplatePath(fsys, policyTemplate.TemplatePath)
85+
}
86+
87+
func validateAgentInputTemplatePath(fsys fspath.FS, tmplPath string) error {
88+
templatePath := path.Join("agent", "input", tmplPath)
89+
_, err := fs.Stat(fsys, templatePath)
90+
if err != nil {
91+
if errors.Is(err, os.ErrNotExist) {
92+
return errTemplateNotFound
93+
}
94+
return fmt.Errorf("failed to stat template file %s: %w", fsys.Path(templatePath), err)
95+
}
96+
97+
return nil
98+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package semantic
6+
7+
import (
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
15+
"github.com/elastic/package-spec/v3/code/go/internal/fspath"
16+
)
17+
18+
func TestValidateInputPackagesPolicyTemplates(t *testing.T) {
19+
20+
t.Run("policy_templates_have_template_path", func(t *testing.T) {
21+
d := t.TempDir()
22+
23+
err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755)
24+
require.NoError(t, err)
25+
err = os.WriteFile(filepath.Join(d, "manifest.yml"), []byte(`
26+
type: input
27+
policy_templates:
28+
- name: udp
29+
template_path: udp.yml.hbs
30+
`), 0o644)
31+
require.NoError(t, err)
32+
err = os.WriteFile(filepath.Join(d, "agent", "input", "udp.yml.hbs"), []byte("# UDP template"), 0o644)
33+
require.NoError(t, err)
34+
35+
errs := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d))
36+
require.Empty(t, errs, "expected no validation errors")
37+
38+
})
39+
40+
t.Run("policy_templates_empty_template_path", func(t *testing.T) {
41+
d := t.TempDir()
42+
43+
err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755)
44+
require.NoError(t, err)
45+
err = os.WriteFile(filepath.Join(d, "manifest.yml"), []byte(`
46+
type: input
47+
policy_templates:
48+
- name: udp
49+
`), 0o644)
50+
require.NoError(t, err)
51+
52+
errs := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d))
53+
require.NotEmpty(t, errs, "expected no validation errors")
54+
55+
assert.Len(t, errs, 1)
56+
assert.ErrorIs(t, errs[0], errRequiredTemplatePath)
57+
})
58+
59+
t.Run("policy_templates_missing_template_path", func(t *testing.T) {
60+
d := t.TempDir()
61+
62+
err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755)
63+
require.NoError(t, err)
64+
err = os.WriteFile(filepath.Join(d, "manifest.yml"), []byte(`
65+
type: input
66+
policy_templates:
67+
- name: udp
68+
template_path: missing.yml.hbs
69+
`), 0o644)
70+
require.NoError(t, err)
71+
72+
errs := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d))
73+
require.NotEmpty(t, errs, "expected validation errors")
74+
assert.Len(t, errs, 1)
75+
assert.ErrorIs(t, errs[0], errTemplateNotFound)
76+
})
77+
78+
t.Run("not_input_package_type", func(t *testing.T) {
79+
d := t.TempDir()
80+
81+
err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755)
82+
require.NoError(t, err)
83+
err = os.WriteFile(filepath.Join(d, "manifest.yml"), []byte(`
84+
type: integration
85+
policy_templates:
86+
- name: udp
87+
template_path: missing.yml.hbs
88+
`), 0o644)
89+
require.NoError(t, err)
90+
91+
errs := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d))
92+
require.NotEmpty(t, errs, "expected validation errors")
93+
assert.Len(t, errs, 1)
94+
assert.ErrorIs(t, errs[0], errInvalidPackageType)
95+
})
96+
97+
}

code/go/internal/validator/spec.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ func (s Spec) rules(pkgType string, rootSpec spectypes.ItemSpec) validationRules
217217
{fn: semantic.ValidateDocsStructure},
218218
{fn: semantic.ValidateDeploymentModes, types: []string{"integration"}},
219219
{fn: semantic.ValidateDurationVariables, since: semver.MustParse("3.5.0")},
220+
{fn: semantic.ValidateInputPackagesPolicyTemplates, types: []string{"input"}},
220221
}
221222

222223
var validationRules validationRules

code/go/pkg/validator/validator_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,13 @@ func TestValidateFile(t *testing.T) {
310310
"field policy_templates.0.input: Must not be present",
311311
},
312312
},
313+
"bad_input_template_path": {
314+
"manifest.yml",
315+
[]string{
316+
"field policy_templates.0: template_path is required",
317+
"policy template \"sql_query\" references template_path \"\": template_path is required for input type packages",
318+
},
319+
},
313320
}
314321

315322
for pkgName, test := range tests {

spec/changelog.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
- description: Add support for script testing in data streams.
2121
type: enhancement
2222
link: https://github.com/elastic/package-spec/pull/985
23+
- description: Input packages require to define template_path in manifest.
24+
type: enhancement
25+
link: https://github.com/elastic/package-spec/pull/1000
2326
- version: 3.5.0
2427
changes:
2528
- description: Add `duration` variable data type with `min_duration` and `max_duration` validation properties.

spec/input/manifest.spec.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ spec:
9494
- description
9595
- type
9696
- input
97+
- template_path
9798
icons:
9899
$ref: "../integration/manifest.spec.yml#/definitions/icons"
99100
screenshots:

test/packages/bad_duplicated_fields_input/manifest.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ policy_templates:
2727
type: logs
2828
input: log_file
2929
description: Collect sample logs
30+
template_path: input.yml.hbs
3031
vars:
3132
- name: paths
3233
required: true

0 commit comments

Comments
 (0)