-
Notifications
You must be signed in to change notification settings - Fork 86
Handlebars template validation and documentation #1030
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
base: main
Are you sure you want to change the base?
Changes from 7 commits
d6d204e
ea6d4bd
67389f6
97a32b2
f5854ea
32ebc29
e76c19a
49c9c5a
86281bb
3e48130
32ab54d
bc7eee5
0f1ae93
d4898fe
d6ee0cd
ab1a541
0d298e7
e699759
31644e8
62679a5
17b18db
bd2358e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| // or more contributor license agreements. Licensed under the Elastic License; | ||
| // you may not use this file except in compliance with the Elastic License. | ||
|
|
||
| package semantic | ||
|
|
||
| import ( | ||
| "errors" | ||
| "io/fs" | ||
| "strings" | ||
|
|
||
| "github.com/elastic/package-spec/v3/code/go/internal/fspath" | ||
| "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" | ||
| "github.com/mailgun/raymond/v2" | ||
| ) | ||
|
|
||
| var ( | ||
| errInvalidHandlebarsTemplate = errors.New("invalid handlebars template") | ||
| ) | ||
|
|
||
| // ValidateHandlebarsFiles validates all Handlebars (.hbs) files in the package filesystem. | ||
| // It returns a list of validation errors if any Handlebars files are invalid. | ||
| // hbs are located in both the package root and data stream directories under the agent folder. | ||
| func ValidateHandlebarsFiles(fsys fspath.FS) specerrors.ValidationErrors { | ||
| entries, err := getHandlebarsFiles(fsys) | ||
| if err != nil { | ||
| return specerrors.ValidationErrors{ | ||
| specerrors.NewStructuredErrorf( | ||
| "error finding Handlebars files: %w", err, | ||
| ), | ||
| } | ||
| } | ||
| if len(entries) == 0 { | ||
| return nil | ||
| } | ||
|
|
||
| var validationErrors specerrors.ValidationErrors | ||
| for _, entry := range entries { | ||
| if !strings.HasSuffix(entry, ".hbs") { | ||
teresaromero marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| continue | ||
| } | ||
|
|
||
| filePath := fsys.Path(entry) | ||
| err := validateFile(filePath) | ||
| if err != nil { | ||
| validationErrors = append(validationErrors, specerrors.NewStructuredErrorf( | ||
| "%w: file %s: %w", errInvalidHandlebarsTemplate, entry, err, | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| return validationErrors | ||
| } | ||
|
|
||
| // validateFile validates a single Handlebars file located at filePath. | ||
| // it parses the file using the raymond library to check for syntax errors. | ||
| func validateFile(filePath string) error { | ||
|
||
| if filePath == "" { | ||
| return nil | ||
| } | ||
| _, err := raymond.ParseFile(filePath) | ||
| return err | ||
| } | ||
|
|
||
| // getHandlebarsFiles returns all Handlebars (.hbs) files in the package filesystem. | ||
| // It searches in both the package root and data stream directories under the agent folder. | ||
| func getHandlebarsFiles(fsys fspath.FS) ([]string, error) { | ||
| entries := make([]string, 0) | ||
| pkgEntries, err := fs.Glob(fsys, "agent/**/*.hbs") | ||
| if err != nil && !errors.Is(err, fs.ErrNotExist) { | ||
| return nil, err | ||
| } | ||
|
||
| entries = append(entries, pkgEntries...) | ||
|
|
||
| dataStreamEntries, err := fs.Glob(fsys, "data_stream/*/agent/**/*.hbs") | ||
| if err != nil && !errors.Is(err, fs.ErrNotExist) { | ||
| return nil, err | ||
| } | ||
| entries = append(entries, dataStreamEntries...) | ||
|
|
||
| return entries, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| // or more contributor license agreements. Licensed under the Elastic License; | ||
| // you may not use this file except in compliance with the Elastic License. | ||
|
|
||
| package semantic | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestValidateFile(t *testing.T) { | ||
|
|
||
| t.Run("no handlebars files", func(t *testing.T) { | ||
| err := validateFile("") | ||
| assert.NoError(t, err) | ||
| }) | ||
|
|
||
| t.Run("valid handlebars files", func(t *testing.T) { | ||
| tmp := t.TempDir() | ||
|
|
||
| filePath := filepath.Join(tmp, "template.yml.hbs") | ||
| err := os.WriteFile(filePath, []byte("{{#if foo}}hello{{/if}}"), 0o644) | ||
| require.NoError(t, err) | ||
|
|
||
| errs := validateFile(filePath) | ||
| assert.Empty(t, errs) | ||
| }) | ||
|
|
||
| t.Run("invalid handlebars files", func(t *testing.T) { | ||
| tmp := t.TempDir() | ||
|
|
||
| filePath := filepath.Join(tmp, "bad.hbs") | ||
| // Unclosed block should produce a parse error. | ||
| err := os.WriteFile(filePath, []byte("{{#if foo}}no end"), 0o644) | ||
| require.NoError(t, err) | ||
|
|
||
| err = validateFile(filePath) | ||
| require.Error(t, err) | ||
| }) | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,24 @@ | ||
| spec: | ||
| additionalContents: false | ||
| contents: | ||
| - description: Folder containing input definitions | ||
| type: folder | ||
| name: input | ||
| required: true | ||
| additionalContents: false | ||
| contents: | ||
| - description: Config template file for inputs defined in the policy_templates section of the top level manifest | ||
| type: file | ||
| sizeLimit: 2MB | ||
| pattern: '^.+\.yml\.hbs$' | ||
| - description: Folder containing input definitions | ||
| type: folder | ||
| name: input | ||
| required: true | ||
| allowLink: true | ||
| additionalContents: false | ||
| contents: | ||
| - description: | | ||
| Config template file for inputs defined in the policy_templates section of the top level manifest. | ||
| The template should use standard Handlebars syntax (e.g., `{{vars.key}}`, `{{#if vars.condition}}`, `{{#each vars.items}}`) | ||
| and must compile to valid YAML. | ||
| Available Handlebars helpers include: | ||
| - `contains` (checks if item is in array/string), | ||
| - `escape_string` (wraps string in single quotes and escapes them), | ||
| - `escape_multiline_string` (escapes multiline strings without wrapping), | ||
| - `to_json` (converts object to JSON string), and | ||
| - `url_encode` (URI encodes string). | ||
|
Comment on lines
+14
to
+19
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These helpers are defined in Fleet (Kibana), it could be added a reference here in the description that the full list of helpers is in Fleet Kibana. At least, I've found that they are defined here:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i took a list from that file and created the text with it, i will add the permlink to the file as a warning that these list can be updated so they can check the fleet code also |
||
| type: file | ||
| sizeLimit: 2MB | ||
| pattern: '^.+\.yml\.hbs$' | ||
| required: true | ||
| allowLink: true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,24 @@ | ||
| spec: | ||
| additionalContents: false | ||
| contents: | ||
| - description: Folder containing input definitions | ||
| type: folder | ||
| name: stream | ||
| required: true | ||
| additionalContents: false | ||
| contents: | ||
| - description: Config template file for inputs defined in the policy_templates section of the top level manifest | ||
| type: file | ||
| sizeLimit: 2MB | ||
| pattern: '^.+\.yml\.hbs$' | ||
| - description: Folder containing input definitions | ||
| type: folder | ||
| name: stream | ||
| required: true | ||
| allowLink: true | ||
| additionalContents: false | ||
| contents: | ||
| - description: | | ||
| Config template file for inputs defined in the policy_templates section of the top level manifest. | ||
| The template should use standard Handlebars syntax (e.g., `{{vars.key}}`, `{{#if vars.condition}}`, `{{#each vars.items}}`) | ||
| and must compile to valid YAML. | ||
| Available Handlebars helpers include: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please document the metadata variables that are available in templates too. See elastic/kibana#241140
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. udpated the pr bd2358e with more docs on variables |
||
| - `contains` (checks if item is in array/string), | ||
| - `escape_string` (wraps string in single quotes and escapes them), | ||
| - `escape_multiline_string` (escapes multiline strings without wrapping), | ||
| - `to_json` (converts object to JSON string), and | ||
| - `url_encode` (URI encodes string). | ||
| type: file | ||
| sizeLimit: 2MB | ||
| pattern: '^.+\.yml\.hbs$' | ||
| required: true | ||
| allowLink: true | ||
Uh oh!
There was an error while loading. Please reload this page.