From a73ef7190b14be417e99710832d6335647ecb0f3 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 11:21:43 +0200 Subject: [PATCH 01/15] Update .gitignore to include .scratch directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3a6105bc53..63bc2851a7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ new_package package-dump .cursor/ +.scratch/ \ No newline at end of file From a2463980cdbf9326b207da3e6fd48f503ff540d8 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 11:22:05 +0200 Subject: [PATCH 02/15] internal/validation: add ValidateSourceFromPath, ValidateBuiltFromPath, ValidateBuiltFromZip Add three new exported functions that use the package-spec ModeSource and ModeBuild constructors instead of the deprecated ValidateFrom* free functions. Existing ValidateAndFilter* functions are kept untouched until callers are migrated. Co-Authored-By: Claude Sonnet 4.6 --- internal/validation/validation.go | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/internal/validation/validation.go b/internal/validation/validation.go index 267e490c13..3b7fc9cb85 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -61,6 +61,76 @@ func ValidateAndFilterFromZip(zipPackagePath string) (error, error) { return result.Processed, result.Removed } +// ValidateSourceFromPath validates a package source tree — checked out from version +// control, not yet built. Source-only artifacts (_dev/, .link files, external: ecs +// references) are permitted. +func ValidateSourceFromPath(packageRoot string) (error, error) { + v, err := validator.NewFromPath(validator.ModeSource, packageRoot) + if err != nil { + return err, nil + } + allErrors := v.Validate() + if allErrors == nil { + return nil, nil + } + fsys := os.DirFS(packageRoot) + result, err := filterErrors(allErrors, fsys) + if err != nil { + return err, nil + } + return result.Processed, result.Removed +} + +// ValidateBuiltFromPath validates a built (unzipped) package directory. Source-only +// artifacts (_dev/, .link files, external: ecs references) are rejected. +func ValidateBuiltFromPath(packageRoot string) (error, error) { + v, err := validator.NewFromPath(validator.ModeBuild, packageRoot) + if err != nil { + return err, nil + } + allErrors := v.Validate() + if allErrors == nil { + return nil, nil + } + fsys := os.DirFS(packageRoot) + result, err := filterErrors(allErrors, fsys) + if err != nil { + return err, nil + } + return result.Processed, result.Removed +} + +// ValidateBuiltFromZip validates a built package zip archive. Zip files are always +// treated as built artifacts; source-only artifacts are rejected. +func ValidateBuiltFromZip(zipPackagePath string) (error, error) { + v, err := validator.NewFromZip(zipPackagePath) + if err != nil { + return fmt.Errorf("failed to open zip for validation (%s): %w", zipPackagePath, err), nil + } + // v.Validate() closes the zip reader it owns. + allErrors := v.Validate() + if allErrors == nil { + return nil, nil + } + // Open a separate, independent zip reader for filterErrors. + fsys, err := zip.OpenReader(zipPackagePath) + if err != nil { + return fmt.Errorf("failed to open zip file (%s): %w", zipPackagePath, err), nil + } + defer fsys.Close() + // fsFromPackageZip navigates into the single package subdirectory so that + // filterErrors can locate validation.yml at the package root, not the zip root. + fsZip, err := fsFromPackageZip(fsys) + if err != nil { + return fmt.Errorf("failed to extract filesystem from zip file (%s): %w", zipPackagePath, err), nil + } + result, err := filterErrors(allErrors, fsZip) + if err != nil { + return err, nil + } + return result.Processed, result.Removed +} + func fsFromPackageZip(fsys fs.FS) (fs.FS, error) { dirs, err := fs.ReadDir(fsys, ".") if err != nil { @@ -77,6 +147,7 @@ func fsFromPackageZip(fsys fs.FS) (fs.FS, error) { return subDir, nil } +// TODO: follow-up issue — move this logic into specerrors as an exported function so consumers don't reimplement it. func filterErrors(allErrors error, fsys fs.FS) (specerrors.FilterResult, error) { var errs specerrors.ValidationErrors if !errors.As(allErrors, &errs) { From 4942eac826c15aa3dbaf14942923f1c720dab786 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 11:26:35 +0200 Subject: [PATCH 03/15] cmd/lint: migrate validateSourceCommandAction to ValidateSourceFromPath Co-Authored-By: Claude Sonnet 4.6 --- cmd/lint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lint.go b/cmd/lint.go index c597da33b1..21b30d7c55 100644 --- a/cmd/lint.go +++ b/cmd/lint.go @@ -83,7 +83,7 @@ func validateSourceCommandAction(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("locating package root failed: %w", err) } - errs, skipped := validation.ValidateAndFilterFromPath(packageRoot) + errs, skipped := validation.ValidateSourceFromPath(packageRoot) if skipped != nil { logger.Infof("Skipped errors: %v", skipped) } From b1e1a843ea982cd5c8173542222ee03b8a4b2fe0 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 11:26:39 +0200 Subject: [PATCH 04/15] internal/validation: document error filtering in godoc for new functions Co-Authored-By: Claude Sonnet 4.6 --- internal/validation/validation.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/internal/validation/validation.go b/internal/validation/validation.go index 3b7fc9cb85..f86ff8099c 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -63,7 +63,9 @@ func ValidateAndFilterFromZip(zipPackagePath string) (error, error) { // ValidateSourceFromPath validates a package source tree — checked out from version // control, not yet built. Source-only artifacts (_dev/, .link files, external: ecs -// references) are permitted. +// references) are permitted. Validation errors are filtered against the package's +// validation.yml config; the first return value is the remaining errors after filtering, +// the second is the errors that were filtered out. func ValidateSourceFromPath(packageRoot string) (error, error) { v, err := validator.NewFromPath(validator.ModeSource, packageRoot) if err != nil { @@ -82,7 +84,10 @@ func ValidateSourceFromPath(packageRoot string) (error, error) { } // ValidateBuiltFromPath validates a built (unzipped) package directory. Source-only -// artifacts (_dev/, .link files, external: ecs references) are rejected. +// artifacts (_dev/, .link files, external: ecs references) are rejected. Validation +// errors are filtered against the package's validation.yml config; the first return +// value is the remaining errors after filtering, the second is the errors that were +// filtered out. func ValidateBuiltFromPath(packageRoot string) (error, error) { v, err := validator.NewFromPath(validator.ModeBuild, packageRoot) if err != nil { @@ -101,7 +106,10 @@ func ValidateBuiltFromPath(packageRoot string) (error, error) { } // ValidateBuiltFromZip validates a built package zip archive. Zip files are always -// treated as built artifacts; source-only artifacts are rejected. +// treated as built artifacts; source-only artifacts are rejected. Validation errors +// are filtered against the package's validation.yml config; the first return value +// is the remaining errors after filtering, the second is the errors that were +// filtered out. func ValidateBuiltFromZip(zipPackagePath string) (error, error) { v, err := validator.NewFromZip(zipPackagePath) if err != nil { From b52e130db0b4e3d4c439f30c3883b4de8f539797 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 11:29:16 +0200 Subject: [PATCH 05/15] cmd/builder,installer: migrate call sites to ValidateBuilt* functions Replace ValidateAndFilterFromPath/ValidateAndFilterFromZip with ValidateBuiltFromPath and ValidateBuiltFromZip in builder and installer packages to use the new named validation mode functions. Co-Authored-By: Claude Sonnet 4.6 --- internal/builder/packages.go | 4 ++-- internal/packages/installer/factory.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/builder/packages.go b/internal/builder/packages.go index f05b355cb1..23766e418b 100644 --- a/internal/builder/packages.go +++ b/internal/builder/packages.go @@ -272,7 +272,7 @@ func BuildPackage(options BuildOptions) (string, error) { } logger.Debugf("Validating built package (path: %s)", buildPackageRoot) - errs, skipped := validation.ValidateAndFilterFromPath(buildPackageRoot) + errs, skipped := validation.ValidateBuiltFromPath(buildPackageRoot) if skipped != nil { logger.Infof("Skipped errors: %v", skipped) } @@ -300,7 +300,7 @@ func buildZippedPackage(options BuildOptions, buildPackageRoot string) (string, logger.Debug("Skip validation of the built .zip package") } else { logger.Debugf("Validating built .zip package (path: %s)", zippedPackagePath) - errs, skipped := validation.ValidateAndFilterFromZip(zippedPackagePath) + errs, skipped := validation.ValidateBuiltFromZip(zippedPackagePath) if skipped != nil { logger.Infof("Skipped errors: %v", skipped) } diff --git a/internal/packages/installer/factory.go b/internal/packages/installer/factory.go index d7a3a7bbec..8b4357ca02 100644 --- a/internal/packages/installer/factory.go +++ b/internal/packages/installer/factory.go @@ -80,7 +80,7 @@ func NewForPackage(options Options) (Installer, error) { logger.Debug("Skip validation of the built .zip package") } else { logger.Debugf("Validating built .zip package (path: %s)", options.ZipPath) - errs, skipped := validation.ValidateAndFilterFromZip(options.ZipPath) + errs, skipped := validation.ValidateBuiltFromZip(options.ZipPath) if skipped != nil { logger.Infof("Skipped errors: %v", skipped) } From 5a34ddbdd3248a3d83c44029242982489276c929 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 11:32:46 +0200 Subject: [PATCH 06/15] internal/validation: remove dead ValidateFromPath/Zip and ValidateAndFilter* functions All call sites have been migrated to the named ValidateSource* and ValidateBuilt* functions, so the legacy wrappers are no longer referenced. Co-Authored-By: Claude Sonnet 4.6 --- internal/validation/validation.go | 46 ------------------------------- 1 file changed, 46 deletions(-) diff --git a/internal/validation/validation.go b/internal/validation/validation.go index f86ff8099c..f0bffdea32 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -15,52 +15,6 @@ import ( "github.com/elastic/package-spec/v3/code/go/pkg/validator" ) -func ValidateFromPath(rootPath string) error { - return validator.ValidateFromPath(rootPath) -} - -func ValidateFromZip(packagePath string) error { - return validator.ValidateFromZip(packagePath) -} - -func ValidateAndFilterFromPath(packageRoot string) (error, error) { - allErrors := validator.ValidateFromPath(packageRoot) - if allErrors == nil { - return nil, nil - } - - fsys := os.DirFS(packageRoot) - result, err := filterErrors(allErrors, fsys) - if err != nil { - return err, nil - } - return result.Processed, result.Removed -} - -func ValidateAndFilterFromZip(zipPackagePath string) (error, error) { - allErrors := validator.ValidateFromZip(zipPackagePath) - if allErrors == nil { - return nil, nil - } - - fsys, err := zip.OpenReader(zipPackagePath) - if err != nil { - return fmt.Errorf("failed to open zip file (%s): %w", zipPackagePath, err), nil - } - defer fsys.Close() - - fsZip, err := fsFromPackageZip(fsys) - if err != nil { - return fmt.Errorf("failed to extract filesystem from zip file (%s): %w", zipPackagePath, err), nil - } - - result, err := filterErrors(allErrors, fsZip) - if err != nil { - return err, nil - } - return result.Processed, result.Removed -} - // ValidateSourceFromPath validates a package source tree — checked out from version // control, not yet built. Source-only artifacts (_dev/, .link files, external: ecs // references) are permitted. Validation errors are filtered against the package's From 9cf389c8bf557641453b503e5bd32674a7b330fd Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 11:59:06 +0200 Subject: [PATCH 07/15] internal/validation: add tests for new validation functions Four table-driven test functions (ValidateSourceFromPath, ValidateBuiltFromPath, ValidateBuiltFromZip) with local testdata fixtures covering both happy-path and cross-mode rejection cases: - source_package: has _dev/, passes ModeSource, fails ModeBuild (ValidateNoDevFolder) - built_package: has _embedded_ecs dynamic templates, passes ModeBuild, fails ModeSource (ValidateNoEmbeddedEcsInDynamicTemplates) - composable_source_package: uses package: stream references, passes ModeSource, fails ModeBuild (ValidateStreamInputMaterialized) Co-Authored-By: Claude Sonnet 4.6 --- go.mod | 2 + .../validation/testdata/built_package.zip | Bin 0 -> 4033 bytes .../testdata/built_package/LICENSE.txt | 3 + .../testdata/built_package/changelog.yml | 6 ++ .../events/agent/stream/stream.yml.hbs | 4 + .../data_stream/events/fields/base-fields.yml | 12 +++ .../data_stream/events/fields/fields.yml | 3 + .../data_stream/events/manifest.yml | 23 +++++ .../testdata/built_package/docs/README.md | 3 + .../testdata/built_package/manifest.yml | 23 +++++ .../composable_source_package/LICENSE.txt | 3 + .../_dev/build/docs/README.md | 3 + .../composable_source_package/changelog.yml | 6 ++ .../events/agent/stream/stream.yml.hbs | 4 + .../data_stream/events/fields/base-fields.yml | 12 +++ .../data_stream/events/manifest.yml | 6 ++ .../composable_source_package/docs/README.md | 3 + .../composable_source_package/manifest.yml | 26 ++++++ .../testdata/source_package/LICENSE.txt | 3 + .../source_package/_dev/build/docs/README.md | 3 + .../testdata/source_package/changelog.yml | 6 ++ .../events/agent/stream/stream.yml.hbs | 4 + .../data_stream/events/fields/base-fields.yml | 12 +++ .../data_stream/events/fields/fields.yml | 3 + .../data_stream/events/manifest.yml | 15 ++++ .../testdata/source_package/docs/README.md | 3 + .../testdata/source_package/manifest.yml | 23 +++++ internal/validation/validation_test.go | 80 ++++++++++++++++++ 28 files changed, 294 insertions(+) create mode 100644 internal/validation/testdata/built_package.zip create mode 100644 internal/validation/testdata/built_package/LICENSE.txt create mode 100644 internal/validation/testdata/built_package/changelog.yml create mode 100644 internal/validation/testdata/built_package/data_stream/events/agent/stream/stream.yml.hbs create mode 100644 internal/validation/testdata/built_package/data_stream/events/fields/base-fields.yml create mode 100644 internal/validation/testdata/built_package/data_stream/events/fields/fields.yml create mode 100644 internal/validation/testdata/built_package/data_stream/events/manifest.yml create mode 100644 internal/validation/testdata/built_package/docs/README.md create mode 100644 internal/validation/testdata/built_package/manifest.yml create mode 100644 internal/validation/testdata/composable_source_package/LICENSE.txt create mode 100644 internal/validation/testdata/composable_source_package/_dev/build/docs/README.md create mode 100644 internal/validation/testdata/composable_source_package/changelog.yml create mode 100644 internal/validation/testdata/composable_source_package/data_stream/events/agent/stream/stream.yml.hbs create mode 100644 internal/validation/testdata/composable_source_package/data_stream/events/fields/base-fields.yml create mode 100644 internal/validation/testdata/composable_source_package/data_stream/events/manifest.yml create mode 100644 internal/validation/testdata/composable_source_package/docs/README.md create mode 100644 internal/validation/testdata/composable_source_package/manifest.yml create mode 100644 internal/validation/testdata/source_package/LICENSE.txt create mode 100644 internal/validation/testdata/source_package/_dev/build/docs/README.md create mode 100644 internal/validation/testdata/source_package/changelog.yml create mode 100644 internal/validation/testdata/source_package/data_stream/events/agent/stream/stream.yml.hbs create mode 100644 internal/validation/testdata/source_package/data_stream/events/fields/base-fields.yml create mode 100644 internal/validation/testdata/source_package/data_stream/events/fields/fields.yml create mode 100644 internal/validation/testdata/source_package/data_stream/events/manifest.yml create mode 100644 internal/validation/testdata/source_package/docs/README.md create mode 100644 internal/validation/testdata/source_package/manifest.yml create mode 100644 internal/validation/validation_test.go diff --git a/go.mod b/go.mod index 9a37edaf49..82e6afff08 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,8 @@ require ( k8s.io/client-go v0.36.1 ) +replace github.com/elastic/package-spec/v3 => ../package-spec + require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.20.0 // indirect diff --git a/internal/validation/testdata/built_package.zip b/internal/validation/testdata/built_package.zip new file mode 100644 index 0000000000000000000000000000000000000000..3db0c1325055541b2a3501e134e1b1debfd4ae7b GIT binary patch literal 4033 zcmb7Hc{G%39R4&238ySYNkwMNG=`Fh>?h39m=HHh!wgwwk{P1K&6Y}q7Hv|Ad$lt~ zHzM6AGK3bnr=(FS(K0RWIc@j-jLT;}%a}RuEPu@NJiq1r{hpzdG4d)9{Al&N?$`fe z;6)9Z1qH=~Me+lqn86XuP`0U;r!s_DFm*<_2{v!qG+H zu$X*iAder-W^yE$x&TjaEKHSv;o9boiOG&-bNM_8G)sL(IDBN5Yb*;i$Oe}$qxR=g z`5?+pfXvYnkRPj&a|GvK34(b@n^ZWzxkFBO_|{CrOojfgkf?(zSW;_;=>p0+LDMXh zMIDF94GUrO_^_mYIipn5Kl3KH+CbI${g%{w2V0)-{J*r@*%O6a`rK0;J=~uOIM1+s zQ}*4t_f=I?9egrl*@8OvM?NZc&jd+bghp1BdFRO;PgoxxBx%@ANzlrxGAnzNy~c&r zx-)r^UWvsH>lUTJB5m(mJ-VEX!ydml_ZwWYJ$Fs_PV6Ljblxal{)**w1e3PuB>s=i zc^1tN;~!YH8FPKl#jy7KXVy7-(qo9bMRpNj6X>wm7W?oO|%jC=R2z5ZOl&>icL>M5)& z*$fF|N3sU4#UfDC#pt@i43c_10SswNKSyl=^&*3MSx$fyp;to`tap1Qv# zpju;(s5X?dwkGz_k0TMUlMNbLeKamxm&Il-?>JTzVoTp!sZ8^|(Ni2}c%jIy??e9Y z;+IK}ouW+Y`Crq;@2}Z9JMh|$>I?-7r&H%69`r+PB^jfk(xL6y=uuq zY6+v3x{yettbEC!W6uyE@#R(c`cLVMZQl<(5WUC~ChZe`x4|o%_|8Afuq7gH&m$o4 z(4{Z7yMVy!;OLYFX{9Al{{4d2*I$kzsmNd3#m<#NAZ?@ZAdHv@G-L6W^7XGxtHr^zRc6eoU zbA8zqe0fSinOD~8b%vTXzqGSkEXWi zX&2C+_7xt9&S$-PhZ$<;HmP==jtY;pK6s#WaUi5o_K&12f1xe@C-?GbPM0$R;;gsDCPqokfVG(kOZ#P+({6GUXZA{ zX8?kpi>_r1oxJiENwud*!J9>oGw`Av7^Ud!Lo+qVUm_@Qn-1E;4OVlv5^sBL1MPP9VaS~WJb(djv+&5qCvYGiwm=(c4jNj(F=~@L@WaLLIs<`)%5wrZv z@r{n9i*W5#{x{1&WGXFm^I5huQ#nY~jnY|X6gk7FvT&D^!;tS0MD z)jQhM_B^Au=kp1@@~?M#u7)L)TK=uIc-=hQujkko8Vl=BUAiBuc)BRSvb}W%BUo5a zQBo0Xb`W|C+33v@|1Y#Y<`$HXt z)_|vZky;jYh*YMEiy5_891dIB(rRjI+Dc9>Y#}$c-jl-%h2+*Wm2O98#x>9qa+CBi zqq(mcKoCU^k{%) zM1MAv8tE{MAtX(oEHwZ7wee#@yEP=454f_!>wwJJKui*{LGK1QVHY)G9wieC?c5}s zfDFn{teP;Vw2vEK0ot7*6$F4GHEz_yXV&;tMOzqTqR(Swpd2(bfNg}GE4d1z*%@Sj zYVxwPq09{+ht16x^+nqYB>A@qkR#2;_(7vx1ClpKK{{wD4*>{Z4>4k3lA(p(+sGPj zR+J_f*x~>f-nSzHkG8{+%4 Date: Thu, 28 May 2026 11:59:48 +0200 Subject: [PATCH 08/15] go.mod: remove local package-spec replace directive This replace is only needed during local development when working against an unpublished package-spec checkout. It must not be committed. Co-Authored-By: Claude Sonnet 4.6 --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index 82e6afff08..9a37edaf49 100644 --- a/go.mod +++ b/go.mod @@ -55,8 +55,6 @@ require ( k8s.io/client-go v0.36.1 ) -replace github.com/elastic/package-spec/v3 => ../package-spec - require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.20.0 // indirect From e3e5bd504615cadbdf695b717c2f4e13c3416822 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 12:04:24 +0200 Subject: [PATCH 09/15] go.mod: pin package-spec to PR #1175 via local replace The PR branch (teresaromero/package-spec@9c3e7024) cannot be fetched by the module proxy because the /v3 module path prevents Go from resolving fork commits until the PR is merged and the proxy indexes them. Replace with the local checkout, which is already at that exact commit. Remove this directive and bump the require version once PR #1175 merges. Co-Authored-By: Claude Sonnet 4.6 --- go.mod | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go.mod b/go.mod index 9a37edaf49..e6b6698909 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,10 @@ require ( k8s.io/client-go v0.36.1 ) +// Local replace pointing to elastic/package-spec PR #1175 (teresaromero/package-spec@9c3e7024). +// Go cannot fetch fork commits for /v3 modules until the PR is merged and the proxy indexes it. +replace github.com/elastic/package-spec/v3 => ../package-spec + require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.20.0 // indirect From 8703b2b21fab69c7a96019a72f89e49ff13cf978 Mon Sep 17 00:00:00 2001 From: Tere Date: Thu, 28 May 2026 12:26:26 +0200 Subject: [PATCH 10/15] internal/validation: fix filter-error loss and zip root entry count - Extract validateFromPath helper to deduplicate ValidateSourceFromPath and ValidateBuiltFromPath (identical bodies, different mode constant). - Return allErrors unfiltered instead of dropping them when filterErrors fails; log the infrastructure error at debug level so it is not silently lost. - In ValidateBuiltFromZip, return allErrors (unfiltered) if the second zip.OpenReader or fsFromPackageZip call fails, preserving the validation result in all error paths. - Fix fsFromPackageZip to count only directory entries at the zip root; the previous check counted all entries, so a root-level non-directory file (e.g. __MACOSX metadata) would falsely trigger 'single directory expected'. Co-Authored-By: Claude Sonnet 4.6 --- internal/validation/validation.go | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/internal/validation/validation.go b/internal/validation/validation.go index f0bffdea32..63ceda9c55 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -11,6 +11,7 @@ import ( "io/fs" "os" + "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" "github.com/elastic/package-spec/v3/code/go/pkg/validator" ) @@ -21,20 +22,7 @@ import ( // validation.yml config; the first return value is the remaining errors after filtering, // the second is the errors that were filtered out. func ValidateSourceFromPath(packageRoot string) (error, error) { - v, err := validator.NewFromPath(validator.ModeSource, packageRoot) - if err != nil { - return err, nil - } - allErrors := v.Validate() - if allErrors == nil { - return nil, nil - } - fsys := os.DirFS(packageRoot) - result, err := filterErrors(allErrors, fsys) - if err != nil { - return err, nil - } - return result.Processed, result.Removed + return validateFromPath(validator.ModeSource, packageRoot) } // ValidateBuiltFromPath validates a built (unzipped) package directory. Source-only @@ -43,7 +31,11 @@ func ValidateSourceFromPath(packageRoot string) (error, error) { // value is the remaining errors after filtering, the second is the errors that were // filtered out. func ValidateBuiltFromPath(packageRoot string) (error, error) { - v, err := validator.NewFromPath(validator.ModeBuild, packageRoot) + return validateFromPath(validator.ModeBuild, packageRoot) +} + +func validateFromPath(mode validator.Mode, packageRoot string) (error, error) { + v, err := validator.NewFromPath(mode, packageRoot) if err != nil { return err, nil } @@ -54,7 +46,8 @@ func ValidateBuiltFromPath(packageRoot string) (error, error) { fsys := os.DirFS(packageRoot) result, err := filterErrors(allErrors, fsys) if err != nil { - return err, nil + logger.Debugf("failed to filter validation errors: %v", err) + return allErrors, nil } return result.Processed, result.Removed } @@ -77,27 +70,34 @@ func ValidateBuiltFromZip(zipPackagePath string) (error, error) { // Open a separate, independent zip reader for filterErrors. fsys, err := zip.OpenReader(zipPackagePath) if err != nil { - return fmt.Errorf("failed to open zip file (%s): %w", zipPackagePath, err), nil + return allErrors, nil } defer fsys.Close() // fsFromPackageZip navigates into the single package subdirectory so that // filterErrors can locate validation.yml at the package root, not the zip root. fsZip, err := fsFromPackageZip(fsys) if err != nil { - return fmt.Errorf("failed to extract filesystem from zip file (%s): %w", zipPackagePath, err), nil + return allErrors, nil } result, err := filterErrors(allErrors, fsZip) if err != nil { - return err, nil + logger.Debugf("failed to filter validation errors: %v", err) + return allErrors, nil } return result.Processed, result.Removed } func fsFromPackageZip(fsys fs.FS) (fs.FS, error) { - dirs, err := fs.ReadDir(fsys, ".") + entries, err := fs.ReadDir(fsys, ".") if err != nil { return nil, fmt.Errorf("failed to read root directory in zip file fs: %w", err) } + var dirs []fs.DirEntry + for _, e := range entries { + if e.IsDir() { + dirs = append(dirs, e) + } + } if len(dirs) != 1 { return nil, fmt.Errorf("a single directory is expected in zip file, %d found", len(dirs)) } From 86536ed0f3a8cffce324d034394b014f78384d89 Mon Sep 17 00:00:00 2001 From: Tere Date: Tue, 16 Jun 2026 16:33:43 +0200 Subject: [PATCH 11/15] Refactor validation modes in internal validation package Updated the validation functions to use more descriptive mode constants. Changed `ModeSource` to `SourceMode` and `ModeBuild` to `BuildMode`. Improved error handling in the validator creation process to provide clearer error messages. Adjusted validation calls to use the appropriate methods for path and zip validation. --- internal/validation/validation.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/validation/validation.go b/internal/validation/validation.go index 63ceda9c55..33842e683a 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -22,7 +22,7 @@ import ( // validation.yml config; the first return value is the remaining errors after filtering, // the second is the errors that were filtered out. func ValidateSourceFromPath(packageRoot string) (error, error) { - return validateFromPath(validator.ModeSource, packageRoot) + return validateFromPath(validator.SourceMode, packageRoot) } // ValidateBuiltFromPath validates a built (unzipped) package directory. Source-only @@ -31,15 +31,15 @@ func ValidateSourceFromPath(packageRoot string) (error, error) { // value is the remaining errors after filtering, the second is the errors that were // filtered out. func ValidateBuiltFromPath(packageRoot string) (error, error) { - return validateFromPath(validator.ModeBuild, packageRoot) + return validateFromPath(validator.BuildMode, packageRoot) } func validateFromPath(mode validator.Mode, packageRoot string) (error, error) { - v, err := validator.NewFromPath(mode, packageRoot) + v, err := validator.New(mode) if err != nil { - return err, nil + return fmt.Errorf("failed to create validator: %w", err), nil } - allErrors := v.Validate() + allErrors := v.ValidateFromPath(packageRoot) if allErrors == nil { return nil, nil } @@ -58,12 +58,12 @@ func validateFromPath(mode validator.Mode, packageRoot string) (error, error) { // is the remaining errors after filtering, the second is the errors that were // filtered out. func ValidateBuiltFromZip(zipPackagePath string) (error, error) { - v, err := validator.NewFromZip(zipPackagePath) + v, err := validator.New(validator.BuildMode) if err != nil { - return fmt.Errorf("failed to open zip for validation (%s): %w", zipPackagePath, err), nil + return fmt.Errorf("failed to create validator: %w", err), nil } - // v.Validate() closes the zip reader it owns. - allErrors := v.Validate() + + allErrors := v.ValidateFromZip(zipPackagePath) if allErrors == nil { return nil, nil } From 4f5faa4670044679af175a0a5bd663ac68398811 Mon Sep 17 00:00:00 2001 From: Tere Date: Tue, 16 Jun 2026 16:35:28 +0200 Subject: [PATCH 12/15] Remove .scratch entry from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 63bc2851a7..3a6105bc53 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,3 @@ new_package package-dump .cursor/ -.scratch/ \ No newline at end of file From acda4eb01537ae827ba97a6056b050d612ff1093 Mon Sep 17 00:00:00 2001 From: Tere Date: Tue, 16 Jun 2026 17:04:08 +0200 Subject: [PATCH 13/15] Enhance validation tests for built packages --- .../validation/testdata/built_package.zip | Bin 4033 -> 0 bytes internal/validation/validation.go | 2 + internal/validation/validation_test.go | 70 +++++++++++++++++- 3 files changed, 68 insertions(+), 4 deletions(-) delete mode 100644 internal/validation/testdata/built_package.zip diff --git a/internal/validation/testdata/built_package.zip b/internal/validation/testdata/built_package.zip deleted file mode 100644 index 3db0c1325055541b2a3501e134e1b1debfd4ae7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4033 zcmb7Hc{G%39R4&238ySYNkwMNG=`Fh>?h39m=HHh!wgwwk{P1K&6Y}q7Hv|Ad$lt~ zHzM6AGK3bnr=(FS(K0RWIc@j-jLT;}%a}RuEPu@NJiq1r{hpzdG4d)9{Al&N?$`fe z;6)9Z1qH=~Me+lqn86XuP`0U;r!s_DFm*<_2{v!qG+H zu$X*iAder-W^yE$x&TjaEKHSv;o9boiOG&-bNM_8G)sL(IDBN5Yb*;i$Oe}$qxR=g z`5?+pfXvYnkRPj&a|GvK34(b@n^ZWzxkFBO_|{CrOojfgkf?(zSW;_;=>p0+LDMXh zMIDF94GUrO_^_mYIipn5Kl3KH+CbI${g%{w2V0)-{J*r@*%O6a`rK0;J=~uOIM1+s zQ}*4t_f=I?9egrl*@8OvM?NZc&jd+bghp1BdFRO;PgoxxBx%@ANzlrxGAnzNy~c&r zx-)r^UWvsH>lUTJB5m(mJ-VEX!ydml_ZwWYJ$Fs_PV6Ljblxal{)**w1e3PuB>s=i zc^1tN;~!YH8FPKl#jy7KXVy7-(qo9bMRpNj6X>wm7W?oO|%jC=R2z5ZOl&>icL>M5)& z*$fF|N3sU4#UfDC#pt@i43c_10SswNKSyl=^&*3MSx$fyp;to`tap1Qv# zpju;(s5X?dwkGz_k0TMUlMNbLeKamxm&Il-?>JTzVoTp!sZ8^|(Ni2}c%jIy??e9Y z;+IK}ouW+Y`Crq;@2}Z9JMh|$>I?-7r&H%69`r+PB^jfk(xL6y=uuq zY6+v3x{yettbEC!W6uyE@#R(c`cLVMZQl<(5WUC~ChZe`x4|o%_|8Afuq7gH&m$o4 z(4{Z7yMVy!;OLYFX{9Al{{4d2*I$kzsmNd3#m<#NAZ?@ZAdHv@G-L6W^7XGxtHr^zRc6eoU zbA8zqe0fSinOD~8b%vTXzqGSkEXWi zX&2C+_7xt9&S$-PhZ$<;HmP==jtY;pK6s#WaUi5o_K&12f1xe@C-?GbPM0$R;;gsDCPqokfVG(kOZ#P+({6GUXZA{ zX8?kpi>_r1oxJiENwud*!J9>oGw`Av7^Ud!Lo+qVUm_@Qn-1E;4OVlv5^sBL1MPP9VaS~WJb(djv+&5qCvYGiwm=(c4jNj(F=~@L@WaLLIs<`)%5wrZv z@r{n9i*W5#{x{1&WGXFm^I5huQ#nY~jnY|X6gk7FvT&D^!;tS0MD z)jQhM_B^Au=kp1@@~?M#u7)L)TK=uIc-=hQujkko8Vl=BUAiBuc)BRSvb}W%BUo5a zQBo0Xb`W|C+33v@|1Y#Y<`$HXt z)_|vZky;jYh*YMEiy5_891dIB(rRjI+Dc9>Y#}$c-jl-%h2+*Wm2O98#x>9qa+CBi zqq(mcKoCU^k{%) zM1MAv8tE{MAtX(oEHwZ7wee#@yEP=454f_!>wwJJKui*{LGK1QVHY)G9wieC?c5}s zfDFn{teP;Vw2vEK0ot7*6$F4GHEz_yXV&;tMOzqTqR(Swpd2(bfNg}GE4d1z*%@Sj zYVxwPq09{+ht16x^+nqYB>A@qkR#2;_(7vx1ClpKK{{wD4*>{Z4>4k3lA(p(+sGPj zR+J_f*x~>f-nSzHkG8{+%4 Date: Mon, 22 Jun 2026 10:16:38 +0200 Subject: [PATCH 14/15] chore: remove local replace directive for package-spec in go.mod --- go.mod | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.mod b/go.mod index c88c63f47e..e5d93dc464 100644 --- a/go.mod +++ b/go.mod @@ -56,10 +56,6 @@ require ( k8s.io/client-go v0.36.2 ) -// Local replace pointing to elastic/package-spec PR #1175 (teresaromero/package-spec@9c3e7024). -// Go cannot fetch fork commits for /v3 modules until the PR is merged and the proxy indexes it. -replace github.com/elastic/package-spec/v3 => ../package-spec - require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.20.0 // indirect From f243dad63757d47bf8c2278cb7d174ab29e01d0f Mon Sep 17 00:00:00 2001 From: Tere Date: Mon, 22 Jun 2026 10:19:37 +0200 Subject: [PATCH 15/15] fix: restore logger import in validation.go --- internal/validation/validation.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/validation/validation.go b/internal/validation/validation.go index d541b50885..50ccae1048 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -11,9 +11,10 @@ import ( "io/fs" "os" - "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" "github.com/elastic/package-spec/v3/code/go/pkg/validator" + + "github.com/elastic/elastic-package/internal/logger" ) // ValidateSourceFromPath validates a package source tree — checked out from version