From f3a6998941b8e9aa5c6b3d6f673eda2136dc0053 Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 09:57:18 +0200 Subject: [PATCH 1/9] Add validation for input package policy templates and corresponding tests --- ...ate_input_policy_template_template_path.go | 100 ++++++++++++++++++ ...nput_policy_template_template_path_test.go | 96 +++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 code/go/internal/validator/semantic/validate_input_policy_template_template_path.go create mode 100644 code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go new file mode 100644 index 000000000..94b193daa --- /dev/null +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go @@ -0,0 +1,100 @@ +// 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" + "fmt" + "io/fs" + "os" + "path" + + "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 ( + inputPackageType packageType = "input" +) + +var ( + errRequiredTemplatePath = errors.New("template_path is required for input type packages") + errFailedToReadManifest = errors.New("failed to read manifest") + errFailedToParseManifest = errors.New("failed to parse manifest") + errTemplateNotFound = errors.New("template file not found") + errInvalidPackageType = errors.New("invalid package type") +) + +type packageType string + +type inputPolicyTemplate struct { + Name string `yaml:"name"` + TemplatePath string `yaml:"template_path"` // input type packages require this field +} + +type inputPackageManifest struct { // package manifest + Type packageType `yaml:"type"` + PolicyTemplates []inputPolicyTemplate `yaml:"policy_templates"` +} + +// ValidateInputPackagesPolicyTemplates +func ValidateInputPackagesPolicyTemplates(fsys fspath.FS) specerrors.ValidationErrors { + var errs specerrors.ValidationErrors + + manifestPath := "manifest.yml" + data, err := fs.ReadFile(fsys, manifestPath) + if err != nil { + return specerrors.ValidationErrors{ + specerrors.NewStructuredErrorf("file \"%s\" is invalid: %ww", fsys.Path(manifestPath), errFailedToReadManifest)} + } + + var manifest inputPackageManifest + err = yaml.Unmarshal(data, &manifest) + if err != nil { + return specerrors.ValidationErrors{ + specerrors.NewStructuredErrorf("file \"%s\" is invalid: %w", fsys.Path(manifestPath), errFailedToParseManifest)} + } + + if manifest.Type != inputPackageType { + return specerrors.ValidationErrors{ + specerrors.NewStructuredErrorf("file \"%s\" is invalid: expected package type \"%s\", got \"%s\": %w", + fsys.Path(manifestPath), inputPackageType, manifest.Type, errInvalidPackageType)} + } + + for _, policyTemplate := range manifest.PolicyTemplates { + err := validateInputPackagePolicyTemplate(fsys, policyTemplate) + if err != nil { + errs = append(errs, specerrors.NewStructuredErrorf( + "file \"%s\" is invalid: policy template \"%s\" references template_path \"%s\": %w", + fsys.Path(manifestPath), policyTemplate.Name, policyTemplate.TemplatePath, err)) + } + } + + return errs +} + +// validateInputPackagePolicyTemplate validates the template_path at the policy template level for input type packages +// if the template_path is empty, it returns an error as this field is required for input type packages +func validateInputPackagePolicyTemplate(fsys fspath.FS, policyTemplate inputPolicyTemplate) error { + if policyTemplate.TemplatePath == "" { + return errRequiredTemplatePath + } + return validateAgentInputTemplatePath(fsys, policyTemplate.TemplatePath) +} + +func validateAgentInputTemplatePath(fsys fspath.FS, tmplPath string) error { + templatePath := path.Join("agent", "input", tmplPath) + _, err := fs.Stat(fsys, templatePath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errTemplateNotFound + } + return fmt.Errorf("failed to stat template file %s: %w", fsys.Path(templatePath), err) + } + + return nil +} diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go new file mode 100644 index 000000000..97ddb3acd --- /dev/null +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go @@ -0,0 +1,96 @@ +// 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/elastic/package-spec/v3/code/go/internal/fspath" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidateInputPackagesPolicyTemplates(t *testing.T) { + + t.Run("policy_templates_have_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 + template_path: udp.yml.hbs +`), 0o644) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(d, "agent", "input", "udp.yml.hbs"), []byte("# UDP template"), 0o644) + require.NoError(t, err) + + errs := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d)) + require.Empty(t, errs, "expected no validation errors") + + }) + + t.Run("policy_templates_empty_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 := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d)) + require.NotEmpty(t, errs, "expected no validation errors") + + assert.Len(t, errs, 1) + assert.ErrorIs(t, errs[0], errRequiredTemplatePath) + }) + + t.Run("policy_templates_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 + template_path: missing.yml.hbs +`), 0o644) + require.NoError(t, err) + + errs := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d)) + require.NotEmpty(t, errs, "expected validation errors") + assert.Len(t, errs, 1) + assert.ErrorIs(t, errs[0], errTemplateNotFound) + }) + + t.Run("not_input_package_type", 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: integration +policy_templates: + - name: udp + template_path: missing.yml.hbs +`), 0o644) + require.NoError(t, err) + + errs := ValidateInputPackagesPolicyTemplates(fspath.DirFS(d)) + require.NotEmpty(t, errs, "expected validation errors") + assert.Len(t, errs, 1) + assert.ErrorIs(t, errs[0], errInvalidPackageType) + }) + +} From 6711aca453feeaaf904ee226358a15d7f63c234a Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 10:00:03 +0200 Subject: [PATCH 2/9] Add input package policy template validation and update test manifests --- code/go/internal/validator/spec.go | 1 + test/packages/bad_duplicated_fields_input/manifest.yml | 1 + test/packages/missing_required_fields_input/manifest.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/code/go/internal/validator/spec.go b/code/go/internal/validator/spec.go index 0db1c315d..d6dd2ca90 100644 --- a/code/go/internal/validator/spec.go +++ b/code/go/internal/validator/spec.go @@ -217,6 +217,7 @@ func (s Spec) rules(pkgType string, rootSpec spectypes.ItemSpec) validationRules {fn: semantic.ValidateDocsStructure}, {fn: semantic.ValidateDeploymentModes, types: []string{"integration"}}, {fn: semantic.ValidateDurationVariables, since: semver.MustParse("3.5.0")}, + {fn: semantic.ValidateInputPackagesPolicyTemplates, types: []string{"input"}}, } var validationRules validationRules diff --git a/test/packages/bad_duplicated_fields_input/manifest.yml b/test/packages/bad_duplicated_fields_input/manifest.yml index 1158d13b0..988f863b3 100644 --- a/test/packages/bad_duplicated_fields_input/manifest.yml +++ b/test/packages/bad_duplicated_fields_input/manifest.yml @@ -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 diff --git a/test/packages/missing_required_fields_input/manifest.yml b/test/packages/missing_required_fields_input/manifest.yml index b87198d56..feadc48c3 100644 --- a/test/packages/missing_required_fields_input/manifest.yml +++ b/test/packages/missing_required_fields_input/manifest.yml @@ -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 From 4b7eefd64f77d0d7007619db32d29b0ffbd78557 Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 14:38:12 +0200 Subject: [PATCH 3/9] Add template_path to required properties in input package manifest --- spec/input/manifest.spec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/input/manifest.spec.yml b/spec/input/manifest.spec.yml index 926ea0a5b..ecce958dc 100644 --- a/spec/input/manifest.spec.yml +++ b/spec/input/manifest.spec.yml @@ -94,6 +94,7 @@ spec: - description - type - input + - template_path icons: $ref: "../integration/manifest.spec.yml#/definitions/icons" screenshots: From 33763f1f3675581a37271ec90d3da6228fcd6926 Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 14:48:56 +0200 Subject: [PATCH 4/9] Update doc for function and add changelog --- .../semantic/validate_input_policy_template_template_path.go | 2 +- spec/changelog.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go index 94b193daa..1b5e77e99 100644 --- a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go @@ -41,7 +41,7 @@ type inputPackageManifest struct { // package manifest PolicyTemplates []inputPolicyTemplate `yaml:"policy_templates"` } -// ValidateInputPackagesPolicyTemplates +// ValidateInputPackagesPolicyTemplates validates the policy template entries of an input package func ValidateInputPackagesPolicyTemplates(fsys fspath.FS) specerrors.ValidationErrors { var errs specerrors.ValidationErrors diff --git a/spec/changelog.yml b/spec/changelog.yml index 5b1a0f8e3..4bb7d273f 100644 --- a/spec/changelog.yml +++ b/spec/changelog.yml @@ -20,6 +20,9 @@ - description: Add support for script testing in data streams. type: enhancement link: https://github.com/elastic/package-spec/pull/985 + - description: Input packages require to define template_path in manifest. + type: enhancement + link: https://github.com/elastic/package-spec/pull/1000 - version: 3.5.0 changes: - description: Add `duration` variable data type with `min_duration` and `max_duration` validation properties. From 97f7f130c226f78c62dd6867bf0635ff451f8317 Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 14:49:20 +0200 Subject: [PATCH 5/9] Fix import order in validate_input_policy_template_template_path_test.go --- .../validate_input_policy_template_template_path_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go index 97ddb3acd..538ff2bec 100644 --- a/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go @@ -9,9 +9,10 @@ import ( "path/filepath" "testing" - "github.com/elastic/package-spec/v3/code/go/internal/fspath" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/elastic/package-spec/v3/code/go/internal/fspath" ) func TestValidateInputPackagesPolicyTemplates(t *testing.T) { From 203245dbaa2b6230a9fc451062eb9f57e3e75ea6 Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 15:36:03 +0200 Subject: [PATCH 6/9] Enhance policy template validation to check for templates in multiple directories and add corresponding tests --- ...ate_input_policy_template_template_path.go | 40 +++++++++++++---- ...nput_policy_template_template_path_test.go | 45 +++++++++++++++++++ 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go index 1b5e77e99..47bc76e89 100644 --- a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go @@ -8,12 +8,12 @@ import ( "errors" "fmt" "io/fs" - "os" "path" "gopkg.in/yaml.v3" "github.com/elastic/package-spec/v3/code/go/internal/fspath" + "github.com/elastic/package-spec/v3/code/go/internal/pkgpath" "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" ) @@ -83,17 +83,41 @@ func validateInputPackagePolicyTemplate(fsys fspath.FS, policyTemplate inputPoli if policyTemplate.TemplatePath == "" { return errRequiredTemplatePath } - return validateAgentInputTemplatePath(fsys, policyTemplate.TemplatePath) + + if err := validateAgentInputTemplatePath(fsys, policyTemplate.TemplatePath); err != nil { + // fallback to data_stream/*/agent/stream if not found in agent/input + // fleet looks for the template within all the files of the package using endsWith match + if err := validateStreamAgentStreamTemplatePath(fsys, policyTemplate.TemplatePath); err != nil { + return err + } + } + + return nil } +// validateAgentInputTemplatePath checks if the template file exists under agent/input directory func validateAgentInputTemplatePath(fsys fspath.FS, tmplPath string) error { - templatePath := path.Join("agent", "input", tmplPath) - _, err := fs.Stat(fsys, templatePath) + foundPaths, err := pkgpath.Files(fsys, path.Join("agent", "input", tmplPath)) if err != nil { - if errors.Is(err, os.ErrNotExist) { - return errTemplateNotFound - } - return fmt.Errorf("failed to stat template file %s: %w", fsys.Path(templatePath), err) + return fmt.Errorf("failed to find template files: %w", err) + } + + if len(foundPaths) == 0 { + return errTemplateNotFound + } + + return nil +} + +// validateStreamAgentStreamTemplatePath checks if the template file exists under data_stream/*/agent/stream directory +func validateStreamAgentStreamTemplatePath(fsys fspath.FS, tmplPath string) error { + foundPaths, err := pkgpath.Files(fsys, path.Join("data_stream", "*", "agent", "stream", tmplPath)) + if err != nil { + return fmt.Errorf("failed to find template files: %w", err) + } + + if len(foundPaths) == 0 { + return errTemplateNotFound } return nil diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go index 538ff2bec..b30dbdbc7 100644 --- a/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go @@ -95,3 +95,48 @@ policy_templates: }) } +func TestValidateInputPackagePolicyTemplate(t *testing.T) { + t.Run("existing_template_found", func(t *testing.T) { + d := t.TempDir() + + // create directories and template file that should match the pattern "*/*/tmpl" + err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(d, "agent", "input", "udp.yml.hbs"), []byte("# UDP template"), 0o644) + require.NoError(t, err) + + err = validateInputPackagePolicyTemplate(fspath.DirFS(d), inputPolicyTemplate{ + TemplatePath: "udp.yml.hbs", + }) + require.NoError(t, err) + }) + + t.Run("existing_template_found_nested", func(t *testing.T) { + d := t.TempDir() + + // create directories and template file that should match the pattern "*/*/tmpl" + 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", "agent", "stream", "udp.yml.hbs"), []byte("# UDP template"), 0o644) + require.NoError(t, err) + + err = validateInputPackagePolicyTemplate(fspath.DirFS(d), inputPolicyTemplate{ + TemplatePath: "udp.yml.hbs", + }) + require.NoError(t, err) + }) + + t.Run("template_not_found", func(t *testing.T) { + d := t.TempDir() + + // no template file created + err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755) + require.NoError(t, err) + + err = validateInputPackagePolicyTemplate(fspath.DirFS(d), inputPolicyTemplate{ + TemplatePath: "missing.yml.hbs", + }) + require.Error(t, err) + assert.ErrorIs(t, err, errTemplateNotFound) + }) +} From 677b5922bc58bf947e59094b8c59d376978652b0 Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 15:46:02 +0200 Subject: [PATCH 7/9] Revert "Enhance policy template validation to check for templates in multiple directories and add corresponding tests" This reverts commit 203245dbaa2b6230a9fc451062eb9f57e3e75ea6. --- ...ate_input_policy_template_template_path.go | 40 ++++------------- ...nput_policy_template_template_path_test.go | 45 ------------------- 2 files changed, 8 insertions(+), 77 deletions(-) diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go index 47bc76e89..1b5e77e99 100644 --- a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go @@ -8,12 +8,12 @@ import ( "errors" "fmt" "io/fs" + "os" "path" "gopkg.in/yaml.v3" "github.com/elastic/package-spec/v3/code/go/internal/fspath" - "github.com/elastic/package-spec/v3/code/go/internal/pkgpath" "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" ) @@ -83,41 +83,17 @@ func validateInputPackagePolicyTemplate(fsys fspath.FS, policyTemplate inputPoli if policyTemplate.TemplatePath == "" { return errRequiredTemplatePath } - - if err := validateAgentInputTemplatePath(fsys, policyTemplate.TemplatePath); err != nil { - // fallback to data_stream/*/agent/stream if not found in agent/input - // fleet looks for the template within all the files of the package using endsWith match - if err := validateStreamAgentStreamTemplatePath(fsys, policyTemplate.TemplatePath); err != nil { - return err - } - } - - return nil + return validateAgentInputTemplatePath(fsys, policyTemplate.TemplatePath) } -// validateAgentInputTemplatePath checks if the template file exists under agent/input directory func validateAgentInputTemplatePath(fsys fspath.FS, tmplPath string) error { - foundPaths, err := pkgpath.Files(fsys, path.Join("agent", "input", tmplPath)) - if err != nil { - return fmt.Errorf("failed to find template files: %w", err) - } - - if len(foundPaths) == 0 { - return errTemplateNotFound - } - - return nil -} - -// validateStreamAgentStreamTemplatePath checks if the template file exists under data_stream/*/agent/stream directory -func validateStreamAgentStreamTemplatePath(fsys fspath.FS, tmplPath string) error { - foundPaths, err := pkgpath.Files(fsys, path.Join("data_stream", "*", "agent", "stream", tmplPath)) + templatePath := path.Join("agent", "input", tmplPath) + _, err := fs.Stat(fsys, templatePath) if err != nil { - return fmt.Errorf("failed to find template files: %w", err) - } - - if len(foundPaths) == 0 { - 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 diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go index b30dbdbc7..538ff2bec 100644 --- a/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path_test.go @@ -95,48 +95,3 @@ policy_templates: }) } -func TestValidateInputPackagePolicyTemplate(t *testing.T) { - t.Run("existing_template_found", func(t *testing.T) { - d := t.TempDir() - - // create directories and template file that should match the pattern "*/*/tmpl" - err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755) - require.NoError(t, err) - err = os.WriteFile(filepath.Join(d, "agent", "input", "udp.yml.hbs"), []byte("# UDP template"), 0o644) - require.NoError(t, err) - - err = validateInputPackagePolicyTemplate(fspath.DirFS(d), inputPolicyTemplate{ - TemplatePath: "udp.yml.hbs", - }) - require.NoError(t, err) - }) - - t.Run("existing_template_found_nested", func(t *testing.T) { - d := t.TempDir() - - // create directories and template file that should match the pattern "*/*/tmpl" - 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", "agent", "stream", "udp.yml.hbs"), []byte("# UDP template"), 0o644) - require.NoError(t, err) - - err = validateInputPackagePolicyTemplate(fspath.DirFS(d), inputPolicyTemplate{ - TemplatePath: "udp.yml.hbs", - }) - require.NoError(t, err) - }) - - t.Run("template_not_found", func(t *testing.T) { - d := t.TempDir() - - // no template file created - err := os.MkdirAll(filepath.Join(d, "agent", "input"), 0o755) - require.NoError(t, err) - - err = validateInputPackagePolicyTemplate(fspath.DirFS(d), inputPolicyTemplate{ - TemplatePath: "missing.yml.hbs", - }) - require.Error(t, err) - assert.ErrorIs(t, err, errTemplateNotFound) - }) -} From e079f638bf8cd5adcb494af0c13ac33314b3214b Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Fri, 17 Oct 2025 15:54:28 +0200 Subject: [PATCH 8/9] Add test package for failed case --- code/go/pkg/validator/validator_test.go | 7 + .../bad_input_template_path/LICENSE.txt | 202 ++++++++++++++++++ .../_dev/build/build.yml | 4 + .../_dev/build/docs/README.md | 1 + .../_dev/deploy/tf/.terraform.lock.hcl | 8 + .../_dev/deploy/tf/data.json | 3 + .../_dev/deploy/tf/main.tf | 1 + .../_dev/deploy/tf/template.tftpl | 1 + .../_dev/test/config.yml | 7 + .../_dev/test/policy/test-mysql.expected | 39 ++++ .../_dev/test/policy/test-mysql.yml | 4 + .../_dev/test/policy/test-oracle.expected | 39 ++++ .../_dev/test/policy/test-oracle.yml | 6 + .../_dev/test/system/test-default-config.yml | 2 + .../agent/input/input.yml.hbs | 9 + .../bad_input_template_path/changelog.yml | 6 + .../bad_input_template_path/docs/README.md | 1 + .../docs/knowledge_base/README.md | 12 ++ .../docs/knowledge_base/example.md | 19 ++ .../fields/base-fields.yml | 12 ++ .../bad_input_template_path/fields/input.yml | 7 + .../img/sample-logo.svg | 1 + .../img/sample-screenshot.png | Bin 0 -> 18849 bytes .../bad_input_template_path/lifecycle.yml | 1 + .../bad_input_template_path/manifest.yml | 105 +++++++++ .../bad_input_template_path/sample_event.json | 63 ++++++ 26 files changed, 560 insertions(+) create mode 100644 test/packages/bad_input_template_path/LICENSE.txt create mode 100644 test/packages/bad_input_template_path/_dev/build/build.yml create mode 100644 test/packages/bad_input_template_path/_dev/build/docs/README.md create mode 100644 test/packages/bad_input_template_path/_dev/deploy/tf/.terraform.lock.hcl create mode 100644 test/packages/bad_input_template_path/_dev/deploy/tf/data.json create mode 100644 test/packages/bad_input_template_path/_dev/deploy/tf/main.tf create mode 100644 test/packages/bad_input_template_path/_dev/deploy/tf/template.tftpl create mode 100644 test/packages/bad_input_template_path/_dev/test/config.yml create mode 100644 test/packages/bad_input_template_path/_dev/test/policy/test-mysql.expected create mode 100644 test/packages/bad_input_template_path/_dev/test/policy/test-mysql.yml create mode 100644 test/packages/bad_input_template_path/_dev/test/policy/test-oracle.expected create mode 100644 test/packages/bad_input_template_path/_dev/test/policy/test-oracle.yml create mode 100644 test/packages/bad_input_template_path/_dev/test/system/test-default-config.yml create mode 100644 test/packages/bad_input_template_path/agent/input/input.yml.hbs create mode 100644 test/packages/bad_input_template_path/changelog.yml create mode 100644 test/packages/bad_input_template_path/docs/README.md create mode 100644 test/packages/bad_input_template_path/docs/knowledge_base/README.md create mode 100644 test/packages/bad_input_template_path/docs/knowledge_base/example.md create mode 100644 test/packages/bad_input_template_path/fields/base-fields.yml create mode 100644 test/packages/bad_input_template_path/fields/input.yml create mode 100644 test/packages/bad_input_template_path/img/sample-logo.svg create mode 100644 test/packages/bad_input_template_path/img/sample-screenshot.png create mode 100644 test/packages/bad_input_template_path/lifecycle.yml create mode 100644 test/packages/bad_input_template_path/manifest.yml create mode 100644 test/packages/bad_input_template_path/sample_event.json diff --git a/code/go/pkg/validator/validator_test.go b/code/go/pkg/validator/validator_test.go index 90ff17ece..f56b802e5 100644 --- a/code/go/pkg/validator/validator_test.go +++ b/code/go/pkg/validator/validator_test.go @@ -310,6 +310,13 @@ func TestValidateFile(t *testing.T) { "field policy_templates.0.input: Must not be present", }, }, + "bad_input_template_path": { + "manifest.yml", + []string{ + "field policy_templates.0: template_path is required", + "policy template \"sql_query\" references template_path \"\": template_path is required for input type packages", + }, + }, } for pkgName, test := range tests { diff --git a/test/packages/bad_input_template_path/LICENSE.txt b/test/packages/bad_input_template_path/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/test/packages/bad_input_template_path/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/test/packages/bad_input_template_path/_dev/build/build.yml b/test/packages/bad_input_template_path/_dev/build/build.yml new file mode 100644 index 000000000..df467d35b --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/build/build.yml @@ -0,0 +1,4 @@ +dependencies: + ecs: + reference: git@v1.9.2 + import_mappings: false \ No newline at end of file diff --git a/test/packages/bad_input_template_path/_dev/build/docs/README.md b/test/packages/bad_input_template_path/_dev/build/docs/README.md new file mode 100644 index 000000000..1a9dfb44c --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/build/docs/README.md @@ -0,0 +1 @@ +This is a template for the package README. \ No newline at end of file diff --git a/test/packages/bad_input_template_path/_dev/deploy/tf/.terraform.lock.hcl b/test/packages/bad_input_template_path/_dev/deploy/tf/.terraform.lock.hcl new file mode 100644 index 000000000..fbb6f39c1 --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/deploy/tf/.terraform.lock.hcl @@ -0,0 +1,8 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { +} + +provider "registry.terraform.io/hashicorp/local" { +} diff --git a/test/packages/bad_input_template_path/_dev/deploy/tf/data.json b/test/packages/bad_input_template_path/_dev/deploy/tf/data.json new file mode 100644 index 000000000..ada45b78f --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/deploy/tf/data.json @@ -0,0 +1,3 @@ +{ + "a": "data file containing json" +} diff --git a/test/packages/bad_input_template_path/_dev/deploy/tf/main.tf b/test/packages/bad_input_template_path/_dev/deploy/tf/main.tf new file mode 100644 index 000000000..f6a81b458 --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/deploy/tf/main.tf @@ -0,0 +1 @@ +# A Terraform file diff --git a/test/packages/bad_input_template_path/_dev/deploy/tf/template.tftpl b/test/packages/bad_input_template_path/_dev/deploy/tf/template.tftpl new file mode 100644 index 000000000..d9e45e2a0 --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/deploy/tf/template.tftpl @@ -0,0 +1 @@ +A Terraform template file diff --git a/test/packages/bad_input_template_path/_dev/test/config.yml b/test/packages/bad_input_template_path/_dev/test/config.yml new file mode 100644 index 000000000..08b057dc1 --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/test/config.yml @@ -0,0 +1,7 @@ +system: + parallel: true +policy: + parallel: false + skip: + reason: ignoring all system tests + link: https://github.com/elastic/package-spec/issues/1 diff --git a/test/packages/bad_input_template_path/_dev/test/policy/test-mysql.expected b/test/packages/bad_input_template_path/_dev/test/policy/test-mysql.expected new file mode 100644 index 000000000..ae8f7b32c --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/test/policy/test-mysql.expected @@ -0,0 +1,39 @@ +inputs: + - data_stream: + namespace: ep + meta: + package: + name: sql_input + name: test-mysql-sql_input + streams: + - data_stream: + dataset: sql_input.sql_query + elasticsearch: + dynamic_dataset: true + dynamic_namespace: true + type: metrics + driver: mysql + hosts: + - root:test@tcp(localhost:3306)/ + metricsets: + - query + period: 10s + sql_query: SHOW GLOBAL STATUS LIKE 'Innodb_%'; + sql_response_format: variables + type: sql/metrics + use_output: default +output_permissions: + default: + _elastic_agent_checks: + cluster: + - monitor + _elastic_agent_monitoring: + indices: [] + uuid-for-permissions-on-related-indices: + indices: + - names: + - metrics-*-* + privileges: + - auto_configure + - create_doc +secret_references: [] diff --git a/test/packages/bad_input_template_path/_dev/test/policy/test-mysql.yml b/test/packages/bad_input_template_path/_dev/test/policy/test-mysql.yml new file mode 100644 index 000000000..045b81d07 --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/test/policy/test-mysql.yml @@ -0,0 +1,4 @@ +vars: + hosts: + - root:test@tcp(localhost:3306)/ + sql_query: "SHOW GLOBAL STATUS LIKE 'Innodb_%';" diff --git a/test/packages/bad_input_template_path/_dev/test/policy/test-oracle.expected b/test/packages/bad_input_template_path/_dev/test/policy/test-oracle.expected new file mode 100644 index 000000000..2d92b5a7f --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/test/policy/test-oracle.expected @@ -0,0 +1,39 @@ +inputs: + - data_stream: + namespace: ep + meta: + package: + name: sql_input + name: test-oracle-sql_input + streams: + - data_stream: + dataset: sql_input.sql_query + elasticsearch: + dynamic_dataset: true + dynamic_namespace: true + type: metrics + driver: oracle + hosts: + - root:test@tcp(localhost)/ + metricsets: + - query + period: 10s + sql_query: SELECT file_name, file_id, tablespace_name, bytes, status, maxbytes, user_bytes, online_status FROM sys.dba_data_files + sql_response_format: variables + type: sql/metrics + use_output: default +output_permissions: + default: + _elastic_agent_checks: + cluster: + - monitor + _elastic_agent_monitoring: + indices: [] + uuid-for-permissions-on-related-indices: + indices: + - names: + - metrics-*-* + privileges: + - auto_configure + - create_doc +secret_references: [] diff --git a/test/packages/bad_input_template_path/_dev/test/policy/test-oracle.yml b/test/packages/bad_input_template_path/_dev/test/policy/test-oracle.yml new file mode 100644 index 000000000..7e349bd4e --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/test/policy/test-oracle.yml @@ -0,0 +1,6 @@ +vars: + driver: "oracle" + hosts: + - root:test@tcp(localhost)/ + # FIXME: This doesn't work as a multiline block. + sql_query: SELECT file_name, file_id, tablespace_name, bytes, status, maxbytes, user_bytes, online_status FROM sys.dba_data_files diff --git a/test/packages/bad_input_template_path/_dev/test/system/test-default-config.yml b/test/packages/bad_input_template_path/_dev/test/system/test-default-config.yml new file mode 100644 index 000000000..b44bf03ac --- /dev/null +++ b/test/packages/bad_input_template_path/_dev/test/system/test-default-config.yml @@ -0,0 +1,2 @@ +wait_for_data_timeout: 10m +vars: ~ diff --git a/test/packages/bad_input_template_path/agent/input/input.yml.hbs b/test/packages/bad_input_template_path/agent/input/input.yml.hbs new file mode 100644 index 000000000..6eb03670b --- /dev/null +++ b/test/packages/bad_input_template_path/agent/input/input.yml.hbs @@ -0,0 +1,9 @@ +metricsets: ["query"] +period: {{period}} +hosts: +{{#each hosts}} + - {{this}} +{{/each}} +driver: {{driver}} +sql_query: {{sql_query} +sql_response_format: {{sql_response_format}} \ No newline at end of file diff --git a/test/packages/bad_input_template_path/changelog.yml b/test/packages/bad_input_template_path/changelog.yml new file mode 100644 index 000000000..945b7a373 --- /dev/null +++ b/test/packages/bad_input_template_path/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "1.0.0" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/package-spec/pull/455 diff --git a/test/packages/bad_input_template_path/docs/README.md b/test/packages/bad_input_template_path/docs/README.md new file mode 100644 index 000000000..2023396ba --- /dev/null +++ b/test/packages/bad_input_template_path/docs/README.md @@ -0,0 +1 @@ +# SQL Input \ No newline at end of file diff --git a/test/packages/bad_input_template_path/docs/knowledge_base/README.md b/test/packages/bad_input_template_path/docs/knowledge_base/README.md new file mode 100644 index 000000000..5e5d0214e --- /dev/null +++ b/test/packages/bad_input_template_path/docs/knowledge_base/README.md @@ -0,0 +1,12 @@ +# Knowledge Base Directory + +This directory contains markdown files that provide AI assistant context for the integration. + +Markdown files in this directory will be available to AI assistants to provide context when users ask questions about this integration. This helps improve the quality and accuracy of AI-generated responses about the integration. + +## Guidelines for creating knowledge base files + +1. Files must be in markdown format with the `.md` extension +2. Files can have any name, but should be descriptive +3. Nested directories are not allowed +4. Content should be focused on providing helpful context about the integration diff --git a/test/packages/bad_input_template_path/docs/knowledge_base/example.md b/test/packages/bad_input_template_path/docs/knowledge_base/example.md new file mode 100644 index 000000000..15e044814 --- /dev/null +++ b/test/packages/bad_input_template_path/docs/knowledge_base/example.md @@ -0,0 +1,19 @@ +# Example Knowledge Base Content + +This is an example knowledge base article in markdown format for the Elastic integration. + +## Features + +- Provides context to AI assistants +- Supports markdown formatting +- Can include code examples + +```python +def example_function(): + print("This is an example function") + return True +``` + +## Usage + +This knowledge base article can be referenced by AI assistants to help users troubleshoot and understand the integration. diff --git a/test/packages/bad_input_template_path/fields/base-fields.yml b/test/packages/bad_input_template_path/fields/base-fields.yml new file mode 100644 index 000000000..7c798f453 --- /dev/null +++ b/test/packages/bad_input_template_path/fields/base-fields.yml @@ -0,0 +1,12 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: '@timestamp' + type: date + description: Event timestamp. diff --git a/test/packages/bad_input_template_path/fields/input.yml b/test/packages/bad_input_template_path/fields/input.yml new file mode 100644 index 000000000..66f3bf3cb --- /dev/null +++ b/test/packages/bad_input_template_path/fields/input.yml @@ -0,0 +1,7 @@ +- name: input.name + type: constant_keyword + description: Sample field to be added. + value: logs +- name: foo + runtime: true + type: keyword diff --git a/test/packages/bad_input_template_path/img/sample-logo.svg b/test/packages/bad_input_template_path/img/sample-logo.svg new file mode 100644 index 000000000..6268dd88f --- /dev/null +++ b/test/packages/bad_input_template_path/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/packages/bad_input_template_path/img/sample-screenshot.png b/test/packages/bad_input_template_path/img/sample-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a56a3ecc078c38636698cefba33f86291dd178 GIT binary patch literal 18849 zcmeEu^S~#!E#4Tq;}?6chqwB{?k=6jc5D4>l%v(rleJ2Y%tW zDj9g7px}|*e;{M?LDwiK3@FNS(lDRTd-MJYIyUJCN948~OJk1M(DrJyI#iV;P4k~& zFZo35IfQt0RwlUN`48^6(1dv_wm(y1xhEdMld=Y?!%u=fPT_*{3( zwBwz3#qR}_)t>C*jp5@U)Ti~B)Y;qq*TRxZJ7ZRN_^A3TDAEM*@7Ve%(Ro7=1%1B< zVj6GBUTxXev>_^SFA zgKZ=g4aTS}9>Ofj7cSB0WO?gQ)x=+!hs_)b$6#>ScFZ>XAoIX)%Bc|BDC~JFBk0f0 z0NY}6gb)&!qx^FWC(!ji+Kl$V$2|ocA=vN0TM0Y`U?tX+T)c*C zA!IL(T2Vm%MCLa85^if@J@Kkprx8QN5!6eCR@4Oa5S?4-4|ou?90mFCM8D!;n(5xz zO}-*t!TntN>|a$s(kGQg1P-U?hqvGF2_fGvd&~yZ_l3Qf&j~XWa=;>N3#-~#zjzcc z*m18L`A-K2o!d@J>a8SRbm4P&-q1(H>|JgIymDbnJF&@008`=X!P?4DGgZb>voUl^ zNJKgPR4S={)3vuk_{n@=M8q;;aJL>q+VLdTnO=}`&x;1DKjJA3*f*idS{jP5?+;!W zn-^7021Z4zv`Aq`hmX1aid997RNh3fa-@PG(W7TzKa1W&5^y3|lPeETP7j9qXpo4)7%(W0_2 z^Nmq;t@rb1eP3?%kOkH`P%!zTC7ZHjSfNN3*Sb#=3#jB*KpNGNfnRZ{N(6DrW(;B2Bwom<%m?VQP%K+ zsFeF1-(DY}oP@)w^Kw~gPg03q?N;)Ec6^|nikA34T~RynX*z}H>R~qgT$`Zbhn8wzZs$j2fsGN&rOK-mIBBvzD@a8FgbLpL!h5N^u&0wG} zq!#md3MHITv?3@$37J?lc_5*LWJTTjel;IiU-Yq;(g9I^D&KN_NKVS0O~GvB~FzPM6}=4d%fG4Nw4pZshcyLqK@`b8?RhD38haIyr@+8+0r5TC1*C7^WleJ zZN3_ngTD#RQvNL*;qD2H@cBWJbCC#d!}=oKfod5SE9a?!?j%DVt1z@inN}Iy$r+96 zM@P?AC+(`cM;z6J94BYGJ;+P-N#yj$?`G26ydS&OVH?~JY(N4l()Fh+x+DoJ@r<+i zhm^ck@QP`=fLApr62@KyOef~}zuG;(VbDQmw|Wb+oSHSw=%w9R)=et0cY*~ytX)#M zEXlK^p;zM@vTnXn+C1vwP)~TJv|TvDE2($;;EzC5_5IL#H;u z)#CO8)TSzbt8)wHB8$I8KcIojx&GoE)3QNu{CQ+_xBmQ&`mL5-u=BX(hs^hMY^ zae!!*Q;Tr$@(0~GoBJAohGw*d{l8~!aXop87aaSUb2jm)Tk>#$1*cdo5Sl+?oD!l4Og~yX+soottl4 zp4OartUuAN(dD~yLJ}`A1*!D4-|L^hM;`_DM^1KYs-VF(}h(BjRO``b+xV~%O=-)?p z7ciJH7Fnl?V&=ay_AB{oQoa2iR;6$^tiE|-eRCFy|3F@%j#6gUxkZX@?K`F$u#;T< z4IZORpUthmB?U`;zrOkp?P(Rvd5TFRWrBJmVg;KEZvJ+;Q}FRY%QZ?c^&$oPXW+C5 zdN#c>v%U?QuE+hMQdzxS1Q(BT90;29qu#^A?a^)Ui;{TJ;%`nLgm2ew$J4NvREjCJ z$`C7&?tH$CrVG@M3J1-KJw_*9BKeL*JX{ zN+Vg_TXb9^jJO$ZGkXO6BBFDjt~w5`w2TB*z$&1W5Il3IiDs=ZMDt|9iRtKET*wF6 z0Z+|N87p-5Fh)^(*l>OVr5^aY5LW(@PuM>Qo@&)yj6XRkPm1>eTF#Y_c*aRF^ZY5A z9FAU7lKEHG@i{wJMPg;n6z2|69d-)q9@<7t()d-zPy&X zdXG7{Uw{k23)CzzQAXw#iqj<1u~W@K_Ljc#?ukh;fRKHeJ2l~Z+52b2n^bGiDF2oX zm25FLx|4AP8>rAi@koY03lrtS#X?zK591c?2iZ_jjc>0y>q9>fU<08o6zG%z9WK+S zDwZMW4~28wu#ye#V*@#5t^S@NiAA`3{SF$xINmc_WW^u-C9M=H>RQ1>WM=|R!660{ z6E6%DwX`eu<3pkmz7Z=FCRd$(vhDkc3yMnSr)5C*aho)DZ<12$`$TXj<8Z70)|rK7 zXFD8QzksfWZU`qL2K8X{C~TcF{KVW`3Y{IMb&)T9%1V`tv(HY1 z+LXkLyM|3mtLD{x-#hOw-U?sr-iLeHFA|=-sGZ4#hX)atL!a91(tWJc+og&5W}VfZ zpgE7`{5D`~?yGR++y7~xA&eU0N*ZezDjF$> zUeK&1aTFQRg*?v^Z2e7u<`lk$czR6}b6Cl-qA9%A`#A6q0*zyTu)X`3rhjR86NK3= zLdw{+-F}+b2gxd-qF7>Rla}dFkj|L#c|pg5Ni+MRA|BZH(@ME*o<1ijKcoXb%PVfJ ztp_uf=G%kvU((pHcw90Xut=}atA!giM-5By)f40nKp zv7Wdb{;^<}VRvruH~rYr~wEuYY2ov-5Q|p@u3Da9+z7PeIpBAwi?RxnxN3Kt+N9L(LUS%wxY` z>e&1VV;{CYw8DNRlvBH)>!I49SU4R!t3I4=y;mCevPZh!-}~G+F>6hcL_Rli4r zC4(WN)`j$>^S=~GMGR=^)A6wrqi(-x{xK37&Vx!OS6t=KQ2JVZo#GrSODtTe=TVh%*qfF%91nqsMNLNL^Gp|_ zz%I*HUkMQGqb!1eh{{bp|0GSCDbkG_D_d)8<(0r<6-%Qi7qDa7xZjcdZ$?Rth9L!f z$erCcs3<~mtupywbaT8NWZF#v?iZkvqSz3@p`RiXs7P!GUa~-U9hEG(NgI#3BzO-# z!9JWf(;r!*A=@g$f}>wi|6Q@9z8AmYf~x8G%sp>C5cfuJY;hs1o3Ozu^{pH0AFbs%yU)Xy5>Cf?qXiHn*-PAfKDRiy`U0sFSKFsgEZ6_ z9#ma!<#Izr^}_z*>PRSt564u6We*XmZUx^jv*dK; z4zyFZ*ZFSE!00<6!|+#33&R)@RA8V9YRjp$HS9?CGq*xDSDRbX#i;}mateEF{fqTI zt?X}Efkq_Ap*_ETgaikOBbQ|;47}hwX44K`(DUI@C)QiG&6UJ1UmRn*Q@6%e`+x(gpQp74O{;yli8YLCV}qD z4gIyZd_(8ED~WWaeXOb0^r=9=AiDT}by~+$KVF~M{ywbQl zng-h?a_E;yX?DCr4|_h7JMc7>xgWf7Ek-VmH^hCYunVp3{(d{---&%-GZ=rK#V5Jo zJvP8b!2AA5?9)G8gwzB6ze3TU<5*Pqms^Q-?C9-CN~4hb-`U0D@kAkTWn23``cao^ z8IWAp8h7`%ZA+eI?w$sJktq5m>e&0@mQn>2BdpKAxbj1$m$8Z;`!iFvl9($Lb9Ff? zT^6cTZ~HgIeR6R*;G(rzpgsJP41Fx9Df;G6{;k6T(i}&8hX(jHSC@~#X@70h#)g(( z*9vUC+a*b%oAdf1$}Z3NR;|c5nY4^Z51pfqk(tmJbB;Q#ka#tf5eae;-kq$I{xO3<(TI$0lSe-JQzJ*es;il=Kn_?&?E zfLbs{qErPqm)-*ZfwbA*D-shgb|1;X;cH*yA|q8gS=HiosF=-kbdk6--SR+`F^H_` z0*i`J==@XSe=HT;_``G}ulE=H@*3GU*?gVd@h*`eT^GKjI;C@8+h~;(u3bA#b&bN{ zYw>dJ$(;RfHDLlndS`CWOE=g0jOocCc&;w(dOzrLf4-DK*MD@P_;u&CbfMw=#Q-B` zDq8hGwKN-O7(hQA_bP3f5XrZH+@*FGw~ppmDgNWcf|Lf*Pc%e5dw1DcJ1BWm!z7z3 zr^toEU*P(>G#;_1X}Rz(5lbDtCui%hY^d3lm)kw0vyk zX~K4$AG#7cG`6s2%9g9zsaQ9o?;3yzW4Pt!;NlS zzI#G7tiq&@eV&}qDtY(e$1JwscAfle%Al{3>Nr%``n?`Jac^CdOXUbFgI3;m{RkA~ zokl+lxuw9=%W&MmzA+G%ZdFMMP&N2^6BWjG2Lt|xKx)lMCR@b0n+xgw<)&Dwi?}>- z+$_e|@M;uW@3z6)q&L7bYitZ%huzGqH_qHOr&G5o!?(8TJv_MN1ka|&c6_!Q>#PgHSFoPWiLg|k_{ zQd#Zy&BPkU(0OE5S35!B5qb6%T3Wd#J(zBl8dw6I#xIDDF-LBPi-jXv1E?!gE|1OIdTejK)+U3ooC^otSIRsWZf-`&K}6}s!407Y58zH zK(oYx*7sN1O|Z_1YIJS_H$E@DH(hB4QKNCGQT3PTvwYoe2&8WKi5`5tU-r4!>_V3XUT}N)>8V;+z-!@-IGCKiD>E9RC(K`NMx=;Qp zf$2g^t?)zpU0L!BZi(oE#)^Z_biT*Svh>r#%1=O+Wo37G`Q)4@k#Pe?^mgBIugC)8 zyEICH=`{A~^x#X&%tr-$j|(nXrIrGQYNY+C3M+LO;yUU4-|v>a5#P)XYp>_|C0f0n{_p0mvwWmghfd%!Cm}$qBDxOqA3htLs~ghSA1>6^dVgd~ zVHHBBy6;Pp=El;dkTE=ttp~BoOJ$L@EB3Z37T1kTNG3tm4PY5O-7hP5DA$-k=vV&6 z?RiAm;W~*o)R7!x9>u$&@|&D4xMmJ*y+^-6t!F0u8G~78t&Bs#W>w_NbW>W9M3tXWXRf zI86FWVx%iXXh6MJ>dg#?lNu{K@S#nzMIG4PXQd%!Bvc*H0c7F_Y=adptJr*cHevMQ z%?Xu~q8CFw>^L*S_83kVhq=)hf0%_Lq}SE*g(Da_A{kXVZfAd*YCwp~bG32wi&SNM z#QZ7}Ug5-=+s^uqAh_|}gzya<(&E?XAZ%0ybd9nraj?|z1YfPr*{N?Q{ji}YG`T#| z=uwJZHIMlsmevnenT#-)t$L*=2wh|1EYXW?_36TR?L!sUItJVxaC0$Gb|gq4{|4gA z(v0ODFj!T)jc5>65ys)* z7$aBHfbKdz@QJq1b`NT`344*g()$>5*Ey`TPB7WI;|_8o8t9-_4ikFub|I{66>ge> zHA+6onzFKY*eaiA!77SD*^&LyumAR6gSvxY6Q?;!AvI{rZ##!G$%ZfIgce4F`aF;e z?jVh%+B-vj69ei~bh_zA9w}S4B4rzRKQ1~u$gwVu_x5PlRKDXX2(_2Mm7fs%6{SS7Qh1gWT8xaxc=f8`mW38ukIZxwU;lmHABwFSg50*o zrj%f%j~IKR?N5Dxwrq|sTa?!pd{b3sFM&~{4~_^YH4$bI^Fq2W4-y`))^|7fS?i0) zJ&Z9wY!8%l7@gAr`2{fqA;L;ptQR*X2|xUtrT47KK%XN+dydN$*M?65LuXTRabgERR{n>;E;(&vS0_@COY!p<%5LsRqGpER%~YjkSK zwBo9-2|-ZFiU3TT&S+@}3gDT35t0IXTzX@yHA(v>Y8;-mZNySQ&fE7RJ1^tzJfvdApX& z*!+tE)Y{oR%jk8A)3EiI3i*(TOwP!;B3hAOj?KQ6^h-q~1V^166uYS~mH*2Hh*0}r z`R3u1#^LG9IW|^QT^|61H(T1Jz?n;(Z>52lU0BO>Q6*zgpP*gTFk2Uw)!3zt>3F~_ ztil4!R*-j}wjh%&(kSB%}X=u4RbFRp@^l+$SmM@nW9B;yGbf@nasjFMEE{m9Oe

}qal5$moSACwfNXLXG5|3R0AtBcN` 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 literal 0 HcmV?d00001 diff --git a/test/packages/bad_input_template_path/lifecycle.yml b/test/packages/bad_input_template_path/lifecycle.yml new file mode 100644 index 000000000..b56a81e81 --- /dev/null +++ b/test/packages/bad_input_template_path/lifecycle.yml @@ -0,0 +1 @@ +data_retention: "30d" diff --git a/test/packages/bad_input_template_path/manifest.yml b/test/packages/bad_input_template_path/manifest.yml new file mode 100644 index 000000000..5bc5afc57 --- /dev/null +++ b/test/packages/bad_input_template_path/manifest.yml @@ -0,0 +1,105 @@ +format_version: 2.12.0 +name: good_input +title: good_input +description: >- + Execute custom queries against an SQL database and store the results in Elasticsearch. +type: input +version: 1.0.0 +source: + license: "Apache-2.0" +conditions: + elastic.subscription: basic + elastic.capabilities: [observability] + kibana: + version: "^8.10.0" +categories: + - custom + - datastore +policy_templates: + - name: sql_query + type: metrics + title: SQL Query + description: Query the database to capture metrics. + input: sql + fips_compatible: false + vars: + - name: hosts + type: text + title: Hosts + multi: true + required: true + show_user: true + default: + - http://127.0.0.1 + - name: password + type: password + title: Password + show_user: true + secret: true + - name: period + type: text + title: Period + multi: false + required: true + show_user: true + default: 10s + - name: driver + type: text + title: Driver + description: "Supported database drivers: mssql, mysql, oracle, postgres" + multi: false + required: true + show_user: true + default: "mysql" + - name: sql_query + type: text + title: Query + multi: false + required: true + show_user: true + default: "SHOW GLOBAL STATUS LIKE 'Innodb_system%'" + - name: sql_response_format + type: text + title: Response format + description: "Supported response formats: variables, table" + multi: false + required: true + show_user: false + default: "variables" + - name: sql_response_caching + type: select + title: Response caching + description: "Optional response caching time" + multi: false + required: true + show_user: false + options: + - value: "0" + text: None + - value: "1" + text: "1 day" +icons: + - src: "/img/sample-logo.svg" + type: "image/svg+xml" +screenshots: + - src: "/img/sample-screenshot.png" + title: "Sample screenshot" + size: "600x600" + type: "image/png" +owner: + github: elastic/integrations +agent: + privileges: + root: true +elasticsearch: + index_template: + mappings: + properties: + '@timestamp': + ignore_malformed: false + type: date + dynamic_templates: + - data_stream_to_constant: + mapping: + type: constant_keyword + path_match: data_stream.* diff --git a/test/packages/bad_input_template_path/sample_event.json b/test/packages/bad_input_template_path/sample_event.json new file mode 100644 index 000000000..617212c23 --- /dev/null +++ b/test/packages/bad_input_template_path/sample_event.json @@ -0,0 +1,63 @@ +{ + "@timestamp": "2022-11-16T19:00:58.919Z", + "agent": { + "ephemeral_id": "1f2789fe-4041-4b4d-aac2-076a34c5d24f", + "id": "47bdbac3-731d-4b11-9af6-06fba253dba8", + "name": "docker-fleet-agent", + "type": "metricbeat", + "version": "8.5.0" + }, + "data_stream": { + "dataset": "good_input.sql_query", + "namespace": "ep", + "type": "metrics" + }, + "ecs": { + "version": "8.0.0" + }, + "elastic_agent": { + "id": "47bdbac3-731d-4b11-9af6-06fba253dba8", + "snapshot": true, + "version": "8.5.0" + }, + "event": { + "dataset": "good_input.sql_query", + "duration": 1326395, + "module": "sql" + }, + "host": { + "architecture": "x86_64", + "containerized": false, + "hostname": "docker-fleet-agent", + "id": "0addaca3101a43f4a52be882837fb33d", + "ip": [ + "192.168.192.7" + ], + "mac": [ + "02-42-C0-A8-C0-07" + ], + "name": "docker-fleet-agent", + "os": { + "codename": "focal", + "family": "debian", + "kernel": "5.15.0-50-generic", + "name": "Ubuntu", + "platform": "ubuntu", + "type": "linux", + "version": "20.04.5 LTS (Focal Fossa)" + } + }, + "metricset": { + "name": "query", + "period": 10000 + }, + "service": { + "address": "elastic-package-service-good_input-1:3306", + "type": "sql" + }, + "sql": { + "driver": "mysql", + "metrics": {}, + "query": "SHOW GLOBAL STATUS LIKE 'Innodb_system%'" + } +} \ No newline at end of file From 7e723e344e778c89e9aeb0a38ed74d4b68a4849f Mon Sep 17 00:00:00 2001 From: Teresa Romero Date: Tue, 21 Oct 2025 11:23:30 +0200 Subject: [PATCH 9/9] Refactor package type definition to use string instead of custom type --- .../validate_input_policy_template_template_path.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go index 1b5e77e99..d85b27e51 100644 --- a/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go +++ b/code/go/internal/validator/semantic/validate_input_policy_template_template_path.go @@ -18,7 +18,7 @@ import ( ) const ( - inputPackageType packageType = "input" + inputPackageType string = "input" ) var ( @@ -29,15 +29,13 @@ var ( errInvalidPackageType = errors.New("invalid package type") ) -type packageType string - type inputPolicyTemplate struct { Name string `yaml:"name"` TemplatePath string `yaml:"template_path"` // input type packages require this field } type inputPackageManifest struct { // package manifest - Type packageType `yaml:"type"` + Type string `yaml:"type"` PolicyTemplates []inputPolicyTemplate `yaml:"policy_templates"` }