Skip to content

bundle templates of required input packages#3329

Open
teresaromero wants to merge 44 commits intoelastic:mainfrom
teresaromero:3278-bundle-templates-required-input-packages
Open

bundle templates of required input packages#3329
teresaromero wants to merge 44 commits intoelastic:mainfrom
teresaromero:3278-bundle-templates-required-input-packages

Conversation

@teresaromero
Copy link
Copy Markdown
Contributor

@teresaromero teresaromero commented Mar 3, 2026

Bundle input package templates for composable integrations

Closes #3278

Composable packages stack — part 1 of 4.
Merge order: this PR → teresaromero#1teresaromero#2teresaromero#3.
Each subsequent PR is stacked on this one and will be rebased onto main once this merges.

Summary

Implements build-time template bundling for integration packages that declare dependencies on input packages via requires.input in their manifest. At build time, elastic-package downloads the required input packages from EPR and copies their agent templates into the integration's build output so Fleet can render them correctly. Both template_path and template_paths fields are handled — input package templates are prepended to the integration's own templates, so the integration takes precedence during rendering.

Bundling runs in two paths:

  • Policy template inputs (bundlePolicyTemplatesInputPackageTemplates): triggered when policy_templates[].inputs[].package references an input package. Templates from agent/input/ are copied to the integration's agent/input/ directory (prefixed with the package name), and the manifest is rewritten to use template_paths.

  • Data stream streams (bundleDataStreamTemplates): triggered when data_stream/<name>/manifest.yml → streams[].package references an input package. Templates from the input package's agent/input/ are copied to the data stream's agent/stream/ directory and template_paths is updated. Bundling is driven solely by this per-stream package reference — Fleet then selects the appropriate templates at render time.

What changed

internal/requiredinputs (new package)

  • RequiredInputsResolver: downloads required input packages from EPR into a temp directory; supports directory and zip formats.
  • bundlePolicyTemplatesInputPackageTemplates: copies and wires input package agent/input/ templates for policy template inputs.
  • bundleDataStreamTemplates: copies and wires input package agent/input/ templates for data stream stream entries.
  • YAML in-place update utilities that preserve comments and formatting.

internal/packages — extended manifest types

  • PolicyTemplate: adds DataStreams, TemplatePaths.
  • PackageManifest.Input: adds Package, TemplatePath, TemplatePaths.
  • DataStreamManifest.Stream: adds Package, TemplatePath, TemplatePaths.
  • New Requires / PackageDependency types; PackageManifest.Requires wired up.

internal/registryDownloadPackage fetches and extracts packages from EPR; TLS support via profile CA cert or env-provided cert.

Build pipelineBundleInputPackageTemplates called during build after package assembly; RequiredInputsResolver threaded through BuildOptions and installer.

Test fixturestest/manual_packages/required_inputs/test_input_pkg (minimal input package) and test/manual_packages/required_inputs/with_input_package_requires (integration consuming it).

How to test

  1. Build the input package so it is available in the local build output:

    cd test/manual_packages/required_inputs/test_input_pkg
    elastic-package build
    
  2. Start the stack from anywhere inside the elastic-package repository so the built package is served by the stack's built-in registry:

    elastic-package stack up --version 9.4.0-SNAPSHOT
    
  3. Build the local tool binary from the repo root, then run it against the integration package:

    go build -o elastic-package .
    cd test/manual_packages/required_inputs/with_input_package_requires
    ../../elastic-package build
    

    Inspect the build/ directory — the input package's agent templates should appear in agent/input/ and data_stream/test_logs/agent/stream/, and the manifests should reference them via template_paths.

Dependencies

Not included

Local source override for input package resolution (bypass EPR during development) — removed pending a cleaner design, to be addressed in a follow-up.

@teresaromero
Copy link
Copy Markdown
Contributor Author

blocked for merge until 3.6 elastic/package-spec#1060

@teresaromero teresaromero marked this pull request as ready for review March 3, 2026 08:49
@teresaromero teresaromero requested a review from a team as a code owner March 3, 2026 08:49
@teresaromero teresaromero marked this pull request as draft March 3, 2026 09:36
Replace the existing package-spec dependency with a specific pre-release
version that includes support for the requires.input manifest section.

NOTE: Remove the replace directive before merging and use the latest
released version when available.

Made-with: Cursor
Add Requires, PackageDependency, and RequiresOverride types. Extend
Stream with PackageRef and TemplatePaths, PolicyTemplate with
TemplatePaths, and PackageManifest with a Requires field.

Add FindPackageInRepo to search the repository for a package by name,
scanning up to 4 directory levels to accommodate monorepo layouts.
Add Client.DownloadPackage which fetches a package by name and version
from the Elastic Package Registry, extracts the zip archive into a
temporary directory, and returns the path to the extracted package root.

Add bundleInputPackageTemplates, which runs after the package content is
copied to the build directory. For each integration that declares
requires.input, it resolves every required input package (from the
registry or a local source override), copies agent input templates into
data_stream/<ds>/agent/stream/ with a "<pkgname>-" prefix, and rewrites
the data stream manifest to use template_paths.

Add test fixtures (test_input_pkg, with_input_package_requires) and unit
tests for the YAML rewriting helpers and the full bundling path.
… pipeline

Thread RequiresOverrides and RegistryURL through FleetPackage,
installer.Options, and BuildOptions so test runners can supply source or
version overrides for required input packages.

Support requires overrides at both top-level and per-type sections in
the global test config (_dev/test/config.yml). Per-type entries override
global entries by package name.

Inject the registry client from app config values to enable local or
custom package registries during the build.

Add an integration test that exercises the full template bundling path
using a local fixture package, bypassing the package registry.
Add a new HOWTO guide explaining how to configure elastic-package to use
a local or custom package registry when developing composable integrations
that declare required input packages.

Update the build command description, README, and dependency management
docs to document the relationship between the build process and the
package registry for composable packages.
@teresaromero teresaromero changed the title builder: bundle templates of required input packages bundle templates of required input packages Mar 4, 2026
@teresaromero teresaromero force-pushed the 3278-bundle-templates-required-input-packages branch from 9b55229 to b43bf5b Compare March 4, 2026 10:30
@teresaromero teresaromero marked this pull request as ready for review March 4, 2026 10:42
@mrodm mrodm requested a review from jsoriano March 10, 2026 17:10
Copy link
Copy Markdown
Member

@jsoriano jsoriano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks!

Comment on lines +10 to +12
This guide explains how to point elastic-package at a local or custom registry, which is
useful when the required input packages are still under development and not yet published to
the production registry.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elastic-package stack up with the compose provider (the default), starts a local package registry, that serves the packages under build/packages. This can be used to serve local packages. You only need to remember building the ones you need.

This mechanism would even be improved after your change in elastic/package-registry#1482, what would allow reloading the packages without needing to restart the registry.

In the documentation here we are adding another method to serve local packages. We should probably converge both methods in a single one.

return "", fmt.Errorf("resolving transform manifests failed: %w", err)
}

err = bundleInputPackageTemplates(options.PackageRoot, buildPackageRoot, options.RegistryClient, options.RequiresOverrides)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a future refactor. We have several methods that receive options.PackageRoot to read the manifest. We could maybe make these methods members of an object that shares the manifest and maybe other parts of a package.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was only required to extract the _dev/test configs... as i've removed this part i will see how to include this param when i implement the testing part

dsRoot := filepath.Dir(dsManifestPath)
agentStreamDir := filepath.Join(dsRoot, "agent", "stream")

// Parse the YAML document preserving formatting for targeted modifications.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preserving the format is nice, though we don't really need to keep the format here, as we are building the package, that is not intended for later human-driven modifications. I would be concerned of complicating things unnecessarily.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean we can modify using the structs and save a new copy from them? i thought we needed to preserve how the manifest was and working directly with yml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the built package to be human-friendly, so we can do what is simpler to maintain.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've gave a try to not use the yaml node aproach, but the models (structs) from elastic-package do not cover all the manifest data... so when marshalling and unmarshalling, data is lost. I was thinking that perhps we should export the models from the spec as a library, and a single point of truth for go structs reflecting the manifest from yaml.

however, i kept the yaml implementation so we just modify the necessary fields, without data loss

Comment on lines +45 to +46
RegistryClient *registry.Client // Registry client for downloading input packages.
RequiresOverrides map[string]packages.RequiresOverride // Pre-merged requires overrides (test builds only).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Maybe for a future change. Instead of receiving the registry and the overrides, this could receive a higher level abstraction that gets dependencies. This abstraction would handle the different package sources, caches, overrides and so on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've removed for now this, i am still wondering how to do the overrides. for now i am injecting an interface with the epr client, i am not sure if there should be an implementation for tests and other for build/install

i am still understanding how this should work, could you provide an example of what should be expected when the override is defined?

…ncies resolution. Enhance local package registry instructions with built-in stack registry usage and standalone registry setup.
@teresaromero teresaromero marked this pull request as draft March 11, 2026 11:32
teresaromero and others added 5 commits March 18, 2026 13:52
…ociation

bundleDataStreamTemplates now reads the integration's root manifest and
builds a dsName→packages lookup from policy templates that explicitly
declare data_streams[]. Stream entries are only bundled when their
package is present in this lookup, preventing implicit 1:1 assumptions
between data streams and policy templates.

Adds TestBundleDataStreamTemplates_SkipWhenNoDataStreamsAssociation,
moves shared test helpers to testhelpers_test.go, and updates the
with_input_package_requires fixture with an explicit data_streams field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bundle templates based solely on the data stream manifest's
streams[].package reference, without requiring an explicit
data_streams association in the root policy template.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@teresaromero teresaromero marked this pull request as ready for review March 19, 2026 09:53
@teresaromero
Copy link
Copy Markdown
Contributor Author

When installing the package or running policy tests for a composable package fleet returns error

Error: can't install the package: could not zip-install package; API status code = 400; response body = {"statusCode":400,"error":"Bad Request","message":"Invalid manifest for data stream test_logs: stream is missing one or more fields of: input, title"}

Implementing tests and running installation of the composable will require #3380

teresaromero and others added 6 commits March 19, 2026 12:18
…esolver

Add NoopRequiredInputsResolver to avoid nil interface panics in integration
tests that don't have an EPR client. Replace filepath.Join/Dir with path.Join/Dir
in policytemplates.go and streams.go so fs.FS and os.Root receive forward-slash
paths on all platforms including Windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Relocate test_input_pkg and with_input_package_requires out of
test/packages so CI build/install zip loops no longer pick them up.
Add test/manual_packages/README.md with manual testing steps.

Made-with: Cursor
Comment on lines +28 to +30
// for each policy template, with an input package reference:
// collect the templates from the input package and copy them to the agent/input directory of the build package
// then update the policy template manifest to include the copied templates as template_paths
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have this process being tested with linked files ?

Checking the spec, template paths could also be linked files. For instance:
https://github.com/elastic/package-spec/blob/73ad6a82946c4880c327b09bb28327bb0d44c0f8/spec/integration/data_stream/agent/spec.yml#L30

Copy link
Copy Markdown
Contributor

@mrodm mrodm Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For packages downloaded from EPR those files should be already resolved, but maybe while testing using local folders (e.g. in system tests), could there be any issue if package contains linked files?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have not tested with linked files yet. the feature for testing and replacing epr for a local source is still WIP

zipPath := filepath.Join(destDir, fmt.Sprintf("%s-%s.zip", name, version))
if err := os.WriteFile(zipPath, zipBytes, 0644); err != nil {
return "", fmt.Errorf("writing package zip %s-%s: %w", name, version, err)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During this process, once the package has been downloaded, should it be validated that package with the corresponding signature (if exists) here too ? signature_path from the package response (metadata in this method). cc @jsoriano

Use os.OpenRoot and root.FS() instead of os.DirFS so reads stay scoped
to the package subtree like the build root path. Callers must close the
root via the returned close function; update openPackageFS godoc accordingly.

Made-with: Cursor
Use a "failed to <action>" pattern across policy template bundling,
data stream bundling, and YAML formatting. Distinguish read versus
parse failures for package manifests. Update tests that assert on
error substrings.

Made-with: Cursor
Use docker.elastic.co/package-registry/package-registry:v1.37.0 to match
PackageRegistryBaseImage. Document distribution:lite as a smaller
alternative and point readers to internal/stack/versions.go for the
canonical tag.

Made-with: Cursor
Comment on lines +36 to +38
type requiredInputsResolver interface {
BundleInputPackageTemplates(buildPackageRoot string) error
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this is duplicated in different packages.
If this is moved for instance under the new requiredinputs package, does it results in circular dependencies?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i used this as a pattern to avoid exporting interfaces and make implementations independent from each other. i've moved the interface to the package as it does not create circular dependencies.

Replace per-package private requiredInputsResolver definitions with a single
exported Resolver interface in internal/requiredinputs. Consumers import
requiredinputs for the contract; *RequiredInputsResolver still implements it.

Made-with: Cursor
@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

History

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bundle templates of required input packages

4 participants