diff --git a/internal/requiredinputs/fields.go b/internal/requiredinputs/fields.go new file mode 100644 index 0000000000..9532181303 --- /dev/null +++ b/internal/requiredinputs/fields.go @@ -0,0 +1,203 @@ +// 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 requiredinputs + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path" + + "gopkg.in/yaml.v3" + + "github.com/elastic/elastic-package/internal/logger" + "github.com/elastic/elastic-package/internal/packages" +) + +// bundleDataStreamFields bundles field definitions from required input packages +// into the composable integration package's data stream fields directories. +// For each data stream that references an input package, fields defined in the +// input package but not already present in the integration's data stream are +// copied into a new file named -fields.yml. +func (r *RequiredInputsResolver) bundleDataStreamFields(inputPkgPaths map[string]string, buildRoot *os.Root) error { + dsManifestsPaths, err := fs.Glob(buildRoot.FS(), "data_stream/*/manifest.yml") + if err != nil { + return fmt.Errorf("globbing data stream manifests: %w", err) + } + + errorList := make([]error, 0) + for _, manifestPath := range dsManifestsPaths { + manifestBytes, err := buildRoot.ReadFile(manifestPath) + if err != nil { + return fmt.Errorf("reading data stream manifest %q: %w", manifestPath, err) + } + manifest, err := packages.ReadDataStreamManifestBytes(manifestBytes) + if err != nil { + return fmt.Errorf("parsing data stream manifest %q: %w", manifestPath, err) + } + for _, stream := range manifest.Streams { + if stream.Package == "" { + continue + } + pkgPath, ok := inputPkgPaths[stream.Package] + if !ok { + errorList = append(errorList, fmt.Errorf("stream in manifest %q references input package %q which is not listed in requires.input", manifestPath, stream.Package)) + continue + } + dsRootDir := path.Dir(manifestPath) + if err := r.mergeInputPkgFields(dsRootDir, pkgPath, stream.Package, buildRoot); err != nil { + return fmt.Errorf("merging input package fields for manifest %q: %w", manifestPath, err) + } + } + } + return errors.Join(errorList...) +} + +// mergeInputPkgFields copies field definitions from the input package into the +// integration's data stream fields directory. Fields already defined in the +// integration take precedence; only fields absent from the integration are +// written to /fields/-fields.yml. +func (r *RequiredInputsResolver) mergeInputPkgFields(dsRootDir, inputPkgPath, inputPkgName string, buildRoot *os.Root) error { + existingNames, err := collectExistingFieldNames(dsRootDir, buildRoot) + if err != nil { + return fmt.Errorf("collecting existing field names: %w", err) + } + + inputPkgFS, closeFn, err := openPackageFS(inputPkgPath) + if err != nil { + return fmt.Errorf("opening package %q: %w", inputPkgPath, err) + } + defer closeFn() + + inputFieldFiles, err := fs.Glob(inputPkgFS, "fields/*.yml") + if err != nil { + return fmt.Errorf("globbing input package fields: %w", err) + } + if len(inputFieldFiles) == 0 { + logger.Debugf("Input package %q has no fields files, skipping field bundling", inputPkgName) + return nil + } + + // Collect field nodes from input package that are not already defined in the integration. + seenNames := make(map[string]bool) + newNodes := make([]*yaml.Node, 0) + for _, filePath := range inputFieldFiles { + nodes, err := loadFieldNodesFromFile(inputPkgFS, filePath) + if err != nil { + return fmt.Errorf("loading field nodes from %q: %w", filePath, err) + } + for _, node := range nodes { + name := fieldNodeName(node) + if name == "" || existingNames[name] || seenNames[name] { + continue + } + seenNames[name] = true + newNodes = append(newNodes, cloneNode(node)) + } + } + + if len(newNodes) == 0 { + logger.Debugf("No new fields from input package %q to bundle into %q", inputPkgName, dsRootDir) + return nil + } + + // Build a YAML document containing the new field nodes as a sequence. + seqNode := &yaml.Node{Kind: yaml.SequenceNode} + seqNode.Content = newNodes + docNode := &yaml.Node{Kind: yaml.DocumentNode, Content: []*yaml.Node{seqNode}} + + output, err := formatYAMLNode(docNode) + if err != nil { + return fmt.Errorf("formatting bundled fields YAML: %w", err) + } + + fieldsDir := path.Join(dsRootDir, "fields") + if err := buildRoot.MkdirAll(fieldsDir, 0755); err != nil { + return fmt.Errorf("creating fields directory %q: %w", fieldsDir, err) + } + + destPath := path.Join(fieldsDir, inputPkgName+"-fields.yml") + if err := buildRoot.WriteFile(destPath, output, 0644); err != nil { + return fmt.Errorf("writing bundled fields to %q: %w", destPath, err) + } + logger.Debugf("Bundled %d field(s) from input package %q into %s", len(newNodes), inputPkgName, destPath) + return nil +} + +// collectExistingFieldNames returns the set of top-level field names already +// defined in the integration's data stream fields directory. +func collectExistingFieldNames(dsRootDir string, buildRoot *os.Root) (map[string]bool, error) { + pattern := path.Join(dsRootDir, "fields", "*.yml") + paths, err := fs.Glob(buildRoot.FS(), pattern) + if err != nil { + return nil, fmt.Errorf("globbing fields in %q: %w", dsRootDir, err) + } + + names := make(map[string]bool) + for _, p := range paths { + data, err := buildRoot.ReadFile(p) + if err != nil { + return nil, fmt.Errorf("reading fields file %q: %w", p, err) + } + nodes, err := loadFieldNodesFromBytes(data) + if err != nil { + return nil, fmt.Errorf("parsing fields file %q: %w", p, err) + } + for _, node := range nodes { + if name := fieldNodeName(node); name != "" { + names[name] = true + } + } + } + return names, nil +} + +// loadFieldNodesFromFile reads a fields YAML file from an fs.FS and returns +// its top-level sequence items as individual yaml.Node pointers. +func loadFieldNodesFromFile(fsys fs.FS, filePath string) ([]*yaml.Node, error) { + data, err := fs.ReadFile(fsys, filePath) + if err != nil { + return nil, fmt.Errorf("reading file %q: %w", filePath, err) + } + return loadFieldNodesFromBytes(data) +} + +// loadFieldNodesFromBytes parses a fields YAML document (expected to be a +// sequence at the document root) and returns the individual item nodes. +func loadFieldNodesFromBytes(data []byte) ([]*yaml.Node, error) { + var doc yaml.Node + if err := yaml.Unmarshal(data, &doc); err != nil { + return nil, fmt.Errorf("unmarshalling fields YAML: %w", err) + } + if doc.Kind == 0 { + // Empty document. + return nil, nil + } + root := &doc + if root.Kind == yaml.DocumentNode { + if len(root.Content) == 0 { + return nil, nil + } + root = root.Content[0] + } + if root.Kind != yaml.SequenceNode { + return nil, fmt.Errorf("expected sequence at fields document root, got kind %v", root.Kind) + } + return root.Content, nil +} + +// fieldNodeName returns the value of the "name" key in a field mapping node, +// or an empty string if the key is absent or the node is nil. +func fieldNodeName(n *yaml.Node) string { + if n == nil { + return "" + } + v := mappingValue(n, "name") + if v == nil { + return "" + } + return v.Value +} diff --git a/internal/requiredinputs/fields_test.go b/internal/requiredinputs/fields_test.go new file mode 100644 index 0000000000..730bbcc5a5 --- /dev/null +++ b/internal/requiredinputs/fields_test.go @@ -0,0 +1,301 @@ +// 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 requiredinputs + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +// ---- unit tests -------------------------------------------------------------- + +// TestLoadFieldNodesFromBytes verifies that field YAML sequences are parsed +// correctly into individual yaml.Node pointers. +func TestLoadFieldNodesFromBytes(t *testing.T) { + t.Run("valid sequence", func(t *testing.T) { + data := []byte(` +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: message + type: text + description: Log message. +`) + nodes, err := loadFieldNodesFromBytes(data) + require.NoError(t, err) + require.Len(t, nodes, 2) + assert.Equal(t, "data_stream.type", fieldNodeName(nodes[0])) + assert.Equal(t, "message", fieldNodeName(nodes[1])) + }) + + t.Run("empty document", func(t *testing.T) { + nodes, err := loadFieldNodesFromBytes([]byte("")) + require.NoError(t, err) + assert.Empty(t, nodes) + }) + + t.Run("invalid YAML", func(t *testing.T) { + _, err := loadFieldNodesFromBytes([]byte(":\t:invalid")) + assert.Error(t, err) + }) + + t.Run("non-sequence root", func(t *testing.T) { + data := []byte(`name: foo\ntype: keyword`) + _, err := loadFieldNodesFromBytes(data) + assert.Error(t, err) + }) +} + +// TestFieldNodeName verifies extraction of the "name" field from a YAML +// mapping node representing a field definition. +func TestFieldNodeName(t *testing.T) { + t.Run("node with name", func(t *testing.T) { + n := &yaml.Node{Kind: yaml.MappingNode} + upsertKey(n, "name", &yaml.Node{Kind: yaml.ScalarNode, Value: "message"}) + assert.Equal(t, "message", fieldNodeName(n)) + }) + + t.Run("node without name", func(t *testing.T) { + n := &yaml.Node{Kind: yaml.MappingNode} + assert.Equal(t, "", fieldNodeName(n)) + }) + + t.Run("nil node", func(t *testing.T) { + assert.Equal(t, "", fieldNodeName(nil)) + }) +} + +// TestCollectExistingFieldNames verifies that field names are collected from +// all YAML files in a data stream's fields/ directory. +func TestCollectExistingFieldNames(t *testing.T) { + t.Run("collects names from multiple files", func(t *testing.T) { + tmpDir := t.TempDir() + buildRoot, err := os.OpenRoot(tmpDir) + require.NoError(t, err) + defer buildRoot.Close() + + require.NoError(t, buildRoot.MkdirAll("data_stream/logs/fields", 0755)) + require.NoError(t, buildRoot.WriteFile("data_stream/logs/fields/base-fields.yml", []byte(` +- name: "@timestamp" + type: date +- name: data_stream.type + type: constant_keyword +`), 0644)) + require.NoError(t, buildRoot.WriteFile("data_stream/logs/fields/extra-fields.yml", []byte(` +- name: message + type: text +`), 0644)) + + names, err := collectExistingFieldNames("data_stream/logs", buildRoot) + require.NoError(t, err) + assert.True(t, names["@timestamp"]) + assert.True(t, names["data_stream.type"]) + assert.True(t, names["message"]) + assert.Len(t, names, 3) + }) + + t.Run("returns empty set when fields directory does not exist", func(t *testing.T) { + tmpDir := t.TempDir() + buildRoot, err := os.OpenRoot(tmpDir) + require.NoError(t, err) + defer buildRoot.Close() + + require.NoError(t, buildRoot.MkdirAll("data_stream/logs", 0755)) + + names, err := collectExistingFieldNames("data_stream/logs", buildRoot) + require.NoError(t, err) + assert.Empty(t, names) + }) +} + +// ---- integration tests ------------------------------------------------------- + +// makeFakeEprForFieldBundling supplies the fields_input_pkg fixture path as if +// it were downloaded from the registry, so integration tests do not need a +// running stack. +func makeFakeEprForFieldBundling(t *testing.T) *fakeEprClient { + t.Helper() + inputPkgPath := filepath.Join("..", "..", "test", "manual_packages", "required_inputs", "fields_input_pkg") + return &fakeEprClient{ + downloadPackageFunc: func(packageName, packageVersion, tmpDir string) (string, error) { + return inputPkgPath, nil + }, + } +} + +// TestBundleDataStreamFields_PartialOverlap verifies the primary field bundling +// scenario: fields already present in the integration data stream are skipped +// (integration wins), and only fields unique to the input package are written +// to /fields/-fields.yml. +func TestBundleDataStreamFields_PartialOverlap(t *testing.T) { + // with_field_bundling has data_stream/field_logs/fields/base-fields.yml with + // 4 common fields. fields_input_pkg has those same 4 plus "message" and + // "log.level". After bundling, only "message" and "log.level" should appear + // in the generated file. + buildPackageRoot := copyFixturePackage(t, "with_field_bundling") + resolver, err := NewRequiredInputsResolver(makeFakeEprForFieldBundling(t)) + require.NoError(t, err) + + err = resolver.Bundle(buildPackageRoot) + require.NoError(t, err) + + bundledPath := filepath.Join(buildPackageRoot, "data_stream", "field_logs", "fields", "fields_input_pkg-fields.yml") + data, err := os.ReadFile(bundledPath) + require.NoError(t, err, "bundled fields file should exist") + + nodes, err := loadFieldNodesFromBytes(data) + require.NoError(t, err) + require.Len(t, nodes, 2) + + names := make([]string, 0, len(nodes)) + for _, n := range nodes { + names = append(names, fieldNodeName(n)) + } + assert.ElementsMatch(t, []string{"message", "log.level"}, names) + + // Original base-fields.yml must be untouched. + originalData, err := os.ReadFile(filepath.Join(buildPackageRoot, "data_stream", "field_logs", "fields", "base-fields.yml")) + require.NoError(t, err) + originalNodes, err := loadFieldNodesFromBytes(originalData) + require.NoError(t, err) + assert.Len(t, originalNodes, 4) +} + +// TestBundleDataStreamFields_AllFieldsOverlap verifies that when all fields in +// the input package are already present in the integration data stream, no +// bundled file is created (nothing to add). +func TestBundleDataStreamFields_AllFieldsOverlap(t *testing.T) { + // with_input_package_requires has data_stream/test_logs/fields/base-fields.yml + // with the same 4 fields as test_input_pkg. No new fields → no output file. + inputPkgPath := filepath.Join("..", "..", "test", "manual_packages", "required_inputs", "test_input_pkg") + epr := &fakeEprClient{ + downloadPackageFunc: func(packageName, packageVersion, tmpDir string) (string, error) { + return inputPkgPath, nil + }, + } + + buildPackageRoot := copyFixturePackage(t, "with_input_package_requires") + resolver, err := NewRequiredInputsResolver(epr) + require.NoError(t, err) + + err = resolver.Bundle(buildPackageRoot) + require.NoError(t, err) + + bundledPath := filepath.Join(buildPackageRoot, "data_stream", "test_logs", "fields", "test_input_pkg-fields.yml") + _, statErr := os.Stat(bundledPath) + assert.True(t, os.IsNotExist(statErr), "bundled fields file should not be created when all fields already exist") +} + +// TestBundleDataStreamFields_NoFieldsInInputPkg verifies that when the input +// package has no fields/ directory, no error occurs and no file is written. +func TestBundleDataStreamFields_NoFieldsInInputPkg(t *testing.T) { + // Create a minimal input package without a fields/ directory. + inputPkgDir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(inputPkgDir, "manifest.yml"), []byte(` +name: no_fields_pkg +version: 0.1.0 +type: input +policy_templates: + - name: t + input: logfile + template_path: input.yml.hbs +`), 0644)) + require.NoError(t, os.MkdirAll(filepath.Join(inputPkgDir, "agent", "input"), 0755)) + require.NoError(t, os.WriteFile(filepath.Join(inputPkgDir, "agent", "input", "input.yml.hbs"), []byte(""), 0644)) + + epr := &fakeEprClient{ + downloadPackageFunc: func(packageName, packageVersion, tmpDir string) (string, error) { + return inputPkgDir, nil + }, + } + + buildPackageRoot := copyFixturePackage(t, "with_field_bundling") + // Patch manifest to reference no_fields_pkg instead. + manifestPath := filepath.Join(buildPackageRoot, "manifest.yml") + manifestData, err := os.ReadFile(manifestPath) + require.NoError(t, err) + patched := []byte(`format_version: 3.6.0 +name: with_field_bundling +title: Integration With Field Bundling +version: 0.1.0 +type: integration +categories: + - custom +conditions: + kibana: + version: "^8.0.0" + elastic: + subscription: basic +requires: + input: + - package: no_fields_pkg + version: "0.1.0" +policy_templates: + - name: field_logs + title: Field Logs + description: Collect logs + data_streams: + - field_logs + inputs: + - package: no_fields_pkg + title: Collect logs + description: Use the no fields input package +owner: + github: elastic/integrations + type: elastic +`) + _ = manifestData // not used further + require.NoError(t, os.WriteFile(manifestPath, patched, 0644)) + + // Also patch the data stream manifest to reference no_fields_pkg. + dsManifestPath := filepath.Join(buildPackageRoot, "data_stream", "field_logs", "manifest.yml") + require.NoError(t, os.WriteFile(dsManifestPath, []byte(`title: Field Logs +type: logs +streams: + - package: no_fields_pkg + title: Field Logs + description: Collect field logs. +`), 0644)) + + resolver, err := NewRequiredInputsResolver(epr) + require.NoError(t, err) + + err = resolver.Bundle(buildPackageRoot) + require.NoError(t, err) + + // No bundled fields file should be created. + bundledPath := filepath.Join(buildPackageRoot, "data_stream", "field_logs", "fields", "no_fields_pkg-fields.yml") + _, statErr := os.Stat(bundledPath) + assert.True(t, os.IsNotExist(statErr), "no fields file should be created when input package has no fields") +} + +// TestBundleDataStreamFields_StreamWithoutPackage verifies that data stream +// streams with no package reference are skipped without error. +func TestBundleDataStreamFields_StreamWithoutPackage(t *testing.T) { + // with_input_package_requires has a second stream with input: logs (no package). + // The test confirms this is processed without error and no unexpected files appear. + inputPkgPath := filepath.Join("..", "..", "test", "manual_packages", "required_inputs", "test_input_pkg") + epr := &fakeEprClient{ + downloadPackageFunc: func(packageName, packageVersion, tmpDir string) (string, error) { + return inputPkgPath, nil + }, + } + + buildPackageRoot := copyFixturePackage(t, "with_input_package_requires") + resolver, err := NewRequiredInputsResolver(epr) + require.NoError(t, err) + + err = resolver.Bundle(buildPackageRoot) + require.NoError(t, err) + + // The non-package stream (logs input) should not produce a bundled fields file. + _, statErr := os.Stat(filepath.Join(buildPackageRoot, "data_stream", "test_logs", "fields", "-fields.yml")) + assert.True(t, os.IsNotExist(statErr)) +} diff --git a/internal/requiredinputs/requiredinputs.go b/internal/requiredinputs/requiredinputs.go index 9d1263fe6b..7d6734ee12 100644 --- a/internal/requiredinputs/requiredinputs.go +++ b/internal/requiredinputs/requiredinputs.go @@ -20,8 +20,9 @@ type eprClient interface { DownloadPackage(packageName string, packageVersion string, tmpDir string) (string, error) } -// Resolver bundles required input package templates into a built package tree and merges -// variables from those input packages when applicable. +// Resolver enriches a built integration package using required input packages from the registry: +// policy and data stream templates, merged manifest variables, and data stream field definitions +// where applicable. type Resolver interface { Bundle(buildPackageRoot string) error } @@ -35,20 +36,25 @@ func (r *NoopRequiredInputsResolver) Bundle(_ string) error { return nil } -// RequiredInputsResolver is a helper for resolving required input packages. +// RequiredInputsResolver implements Resolver by downloading required input packages via an EPR client +// and applying Bundle to the built package tree. type RequiredInputsResolver struct { eprClient eprClient } -// NewRequiredInputsResolver returns a Resolver that downloads required input packages from the registry. +// NewRequiredInputsResolver returns a Resolver backed by eprClient. Required input packages are +// downloaded when Bundle runs. func NewRequiredInputsResolver(eprClient eprClient) (*RequiredInputsResolver, error) { return &RequiredInputsResolver{ eprClient: eprClient, }, nil } +// Bundle updates buildPackageRoot (a built package directory) for integrations that declare +// requires.input: it downloads those input packages, copies policy and data stream templates, +// merges variables into the integration manifest, and bundles data stream field definitions. +// Non-integration packages or packages without requires.input are left unchanged. func (r *RequiredInputsResolver) Bundle(buildPackageRoot string) error { - buildRoot, err := os.OpenRoot(buildPackageRoot) if err != nil { return fmt.Errorf("failed to open build package root: %w", err) @@ -69,7 +75,7 @@ func (r *RequiredInputsResolver) Bundle(buildPackageRoot string) error { return nil } if manifest.Requires == nil || len(manifest.Requires.Input) == 0 { - logger.Debug("Package has no required input packages, skipping template bundling") + logger.Debug("Package has no required input packages, skipping required input processing") return nil } @@ -96,6 +102,10 @@ func (r *RequiredInputsResolver) Bundle(buildPackageRoot string) error { return fmt.Errorf("merging variables from input packages: %w", err) } + if err := r.bundleDataStreamFields(inputPkgPaths, buildRoot); err != nil { + return fmt.Errorf("bundling data stream fields from input packages: %w", err) + } + return nil } diff --git a/test/manual_packages/required_inputs/fields_input_pkg/agent/input/input.yml.hbs b/test/manual_packages/required_inputs/fields_input_pkg/agent/input/input.yml.hbs new file mode 100644 index 0000000000..9e9c27a8c0 --- /dev/null +++ b/test/manual_packages/required_inputs/fields_input_pkg/agent/input/input.yml.hbs @@ -0,0 +1,4 @@ +paths: +{{#each paths}} + - {{this}} +{{/each}} \ No newline at end of file diff --git a/test/manual_packages/required_inputs/fields_input_pkg/changelog.yml b/test/manual_packages/required_inputs/fields_input_pkg/changelog.yml new file mode 100644 index 0000000000..813cf1cf77 --- /dev/null +++ b/test/manual_packages/required_inputs/fields_input_pkg/changelog.yml @@ -0,0 +1,5 @@ +- version: "0.1.0" + changes: + - description: Initial release. + type: enhancement + link: https://github.com/elastic/elastic-package/pull/1 diff --git a/test/manual_packages/required_inputs/fields_input_pkg/docs/README.md b/test/manual_packages/required_inputs/fields_input_pkg/docs/README.md new file mode 100644 index 0000000000..1c1576a01b --- /dev/null +++ b/test/manual_packages/required_inputs/fields_input_pkg/docs/README.md @@ -0,0 +1,3 @@ +# Fields Input Package + +Input package used as a test fixture for field bundling tests. diff --git a/test/manual_packages/required_inputs/fields_input_pkg/fields/base-fields.yml b/test/manual_packages/required_inputs/fields_input_pkg/fields/base-fields.yml new file mode 100644 index 0000000000..2b59a9a276 --- /dev/null +++ b/test/manual_packages/required_inputs/fields_input_pkg/fields/base-fields.yml @@ -0,0 +1,18 @@ +- 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. +- name: message + type: text + description: Log message. +- name: log.level + type: keyword + description: Log level. diff --git a/test/manual_packages/required_inputs/fields_input_pkg/manifest.yml b/test/manual_packages/required_inputs/fields_input_pkg/manifest.yml new file mode 100644 index 0000000000..bf4502f107 --- /dev/null +++ b/test/manual_packages/required_inputs/fields_input_pkg/manifest.yml @@ -0,0 +1,32 @@ +format_version: 3.6.0 +name: fields_input_pkg +title: Fields Input Package +description: Input package used as a test fixture for field bundling. +version: 0.1.0 +type: input +categories: + - custom +conditions: + kibana: + version: "^8.0.0" + elastic: + subscription: basic +policy_templates: + - name: field_bundling + type: logs + title: Field Bundling + description: Collect logs with field bundling. + input: logfile + template_path: input.yml.hbs + vars: + - name: paths + type: text + title: Paths + multi: true + required: true + show_user: true + default: + - /var/log/*.log +owner: + github: elastic/integrations + type: elastic diff --git a/test/manual_packages/required_inputs/with_field_bundling/changelog.yml b/test/manual_packages/required_inputs/with_field_bundling/changelog.yml new file mode 100644 index 0000000000..813cf1cf77 --- /dev/null +++ b/test/manual_packages/required_inputs/with_field_bundling/changelog.yml @@ -0,0 +1,5 @@ +- version: "0.1.0" + changes: + - description: Initial release. + type: enhancement + link: https://github.com/elastic/elastic-package/pull/1 diff --git a/test/manual_packages/required_inputs/with_field_bundling/data_stream/field_logs/fields/base-fields.yml b/test/manual_packages/required_inputs/with_field_bundling/data_stream/field_logs/fields/base-fields.yml new file mode 100644 index 0000000000..0d1791ffed --- /dev/null +++ b/test/manual_packages/required_inputs/with_field_bundling/data_stream/field_logs/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/manual_packages/required_inputs/with_field_bundling/data_stream/field_logs/manifest.yml b/test/manual_packages/required_inputs/with_field_bundling/data_stream/field_logs/manifest.yml new file mode 100644 index 0000000000..826c7c676f --- /dev/null +++ b/test/manual_packages/required_inputs/with_field_bundling/data_stream/field_logs/manifest.yml @@ -0,0 +1,6 @@ +title: Field Logs +type: logs +streams: + - package: fields_input_pkg + title: Field Logs from Input Package + description: Collect field logs using the referenced input package. diff --git a/test/manual_packages/required_inputs/with_field_bundling/docs/README.md b/test/manual_packages/required_inputs/with_field_bundling/docs/README.md new file mode 100644 index 0000000000..87332b2fa6 --- /dev/null +++ b/test/manual_packages/required_inputs/with_field_bundling/docs/README.md @@ -0,0 +1,3 @@ +# Integration With Field Bundling + +Integration package that requires an input package, used to test field bundling. diff --git a/test/manual_packages/required_inputs/with_field_bundling/manifest.yml b/test/manual_packages/required_inputs/with_field_bundling/manifest.yml new file mode 100644 index 0000000000..487ea2e57a --- /dev/null +++ b/test/manual_packages/required_inputs/with_field_bundling/manifest.yml @@ -0,0 +1,33 @@ +format_version: 3.6.0 +name: with_field_bundling +title: Integration With Field Bundling +description: >- + Integration package that requires an input package, used to test field bundling. + The input package defines additional fields (message, log.level) that are not + present in the integration's data stream and should be bundled in. +version: 0.1.0 +type: integration +categories: + - custom +conditions: + kibana: + version: "^8.0.0" + elastic: + subscription: basic +requires: + input: + - package: fields_input_pkg + version: "0.1.0" +policy_templates: + - name: field_logs + title: Field Logs + description: Collect logs via field bundling input package + data_streams: + - field_logs + inputs: + - package: fields_input_pkg + title: Collect logs via field bundling input package + description: Use the field bundling input package to collect logs +owner: + github: elastic/integrations + type: elastic