@@ -6,6 +6,7 @@ package packages
66
77import  (
88	"archive/zip" 
9+ 	"bytes" 
910	"encoding/json" 
1011	"errors" 
1112	"fmt" 
@@ -14,6 +15,7 @@ import (
1415	"path/filepath" 
1516	"slices" 
1617	"strings" 
18+ 	"text/template" 
1719
1820	yamlv3 "gopkg.in/yaml.v3" 
1921
@@ -243,6 +245,9 @@ type TransformDefinition struct {
243245	Source  struct  {
244246		Index  []string  `config:"index" yaml:"index"` 
245247	} `config:"source" yaml:"source"` 
248+ 	Dest  struct  {
249+ 		Pipeline  string  `config:"pipeline" yaml:"pipeline"` 
250+ 	} `config:"dest" yaml:"dest"` 
246251	Meta  struct  {
247252		FleetTransformVersion  string  `config:"fleet_transform_version" yaml:"fleet_transform_version"` 
248253	} `config:"_meta" yaml:"_meta"` 
@@ -414,6 +419,101 @@ func ReadPackageManifest(path string) (*PackageManifest, error) {
414419	return  & m , nil 
415420}
416421
422+ // ReadTransformDefinitionFile reads and parses the transform definition (elasticsearch/transform/<name>/transform.yml) 
423+ // file for the given transform. It also applies templating to the file, allowing to set the final ingest pipeline name 
424+ // by adding the package version defined in the package manifest. 
425+ // It fails if the referenced destination pipeline doesn't exist. 
426+ func  ReadTransformDefinitionFile (transformPath , packageRootPath  string ) ([]byte , TransformDefinition , error ) {
427+ 	manifest , err  :=  ReadPackageManifestFromPackageRoot (packageRootPath )
428+ 	if  err  !=  nil  {
429+ 		return  nil , TransformDefinition {}, fmt .Errorf ("could not read package manifest: %w" , err )
430+ 	}
431+ 
432+ 	if  manifest .Version  ==  ""  {
433+ 		return  nil , TransformDefinition {}, fmt .Errorf ("package version is not defined in the package manifest" )
434+ 	}
435+ 
436+ 	t , err  :=  template .New (filepath .Base (transformPath )).Funcs (template.FuncMap {
437+ 		"ingestPipelineName" : func (pipelineName  string ) (string , error ) {
438+ 			if  pipelineName  ==  ""  {
439+ 				return  "" , fmt .Errorf ("ingest pipeline name is empty" )
440+ 			}
441+ 			return  fmt .Sprintf ("%s-%s" , manifest .Version , pipelineName ), nil 
442+ 		},
443+ 	}).ParseFiles (transformPath )
444+ 	if  err  !=  nil  {
445+ 		return  nil , TransformDefinition {}, fmt .Errorf ("parsing transform template failed (path: %s): %w" , transformPath , err )
446+ 	}
447+ 
448+ 	var  rendered  bytes.Buffer 
449+ 	err  =  t .Execute (& rendered , nil )
450+ 	if  err  !=  nil  {
451+ 		return  nil , TransformDefinition {}, fmt .Errorf ("executing template failed: %w" , err )
452+ 	}
453+ 	cfg , err  :=  yaml .NewConfig (rendered .Bytes (), ucfg .PathSep ("." ))
454+ 	if  err  !=  nil  {
455+ 		return  nil , TransformDefinition {}, fmt .Errorf ("reading file failed (path: %s): %w" , transformPath , err )
456+ 	}
457+ 
458+ 	var  definition  TransformDefinition 
459+ 	err  =  cfg .Unpack (& definition )
460+ 	if  err  !=  nil  {
461+ 		return  nil , TransformDefinition {}, fmt .Errorf ("failed to parse transform file \" %s\" : %w" , transformPath , err )
462+ 	}
463+ 
464+ 	if  definition .Dest .Pipeline  ==  ""  {
465+ 		return  rendered .Bytes (), definition , nil 
466+ 	}
467+ 
468+ 	// Is it using the Ingest pipeline defined in the package (elasticsearch/ingest_pipeline/<version>-<pipeline>.yml)? 
469+ 	// <version>-<pipeline>.yml 
470+ 	// example: 0.1.0-pipeline_extract_metadata 
471+ 
472+ 	pipelineFileName  :=  fmt .Sprintf ("%s.yml" , strings .TrimPrefix (definition .Dest .Pipeline , manifest .Version + "-" ))
473+ 	_ , err  =  os .Stat (filepath .Join (packageRootPath , "elasticsearch" , "ingest_pipeline" , pipelineFileName ))
474+ 	if  err  !=  nil  &&  ! errors .Is (err , os .ErrNotExist ) {
475+ 		return  nil , TransformDefinition {}, fmt .Errorf ("checking for destination ingest pipeline file %s: %w" , pipelineFileName , err )
476+ 	}
477+ 	if  err  ==  nil  {
478+ 		return  rendered .Bytes (), definition , nil 
479+ 	}
480+ 
481+ 	// Is it using the Ingest pipeline from any data stream (data_stream/*/elasticsearch/pipeline/*.yml)? 
482+ 	// <data_stream>-<version>-<data_stream_pipeline>.yml 
483+ 	// example: metrics-aws_billing.cur-0.1.0-pipeline_extract_metadata 
484+ 	dataStreamPaths , err  :=  filepath .Glob (filepath .Join (packageRootPath , "data_stream" , "*" ))
485+ 	if  err  !=  nil  {
486+ 		return  nil , TransformDefinition {}, fmt .Errorf ("error finding data streams: %w" , err )
487+ 	}
488+ 
489+ 	for  _ , dataStreamPath  :=  range  dataStreamPaths  {
490+ 		matched , err  :=  filepath .Glob (filepath .Join (dataStreamPath , "elasticsearch" , "ingest_pipeline" , "*.yml" ))
491+ 		if  err  !=  nil  {
492+ 			return  nil , TransformDefinition {}, fmt .Errorf ("error finding ingest pipelines in data stream %s: %w" , dataStreamPath , err )
493+ 		}
494+ 		dataStreamName  :=  filepath .Base (dataStreamPath )
495+ 		for  _ , pipelinePath  :=  range  matched  {
496+ 			dataStreamPipelineName  :=  strings .TrimSuffix (filepath .Base (pipelinePath ), filepath .Ext (pipelinePath ))
497+ 			expectedSuffix  :=  fmt .Sprintf ("-%s.%s-%s-%s.yml" , manifest .Name , dataStreamName , manifest .Version , dataStreamPipelineName )
498+ 			if  strings .HasSuffix (pipelineFileName , expectedSuffix ) {
499+ 				return  rendered .Bytes (), definition , nil 
500+ 			}
501+ 		}
502+ 	}
503+ 	pipelinePaths , err  :=  filepath .Glob (filepath .Join (packageRootPath , "data_stream" , "*" , "elasticsearch" , "ingest_pipeline" , "*.yml" ))
504+ 	if  err  !=  nil  {
505+ 		return  nil , TransformDefinition {}, fmt .Errorf ("error finding ingest pipelines in data streams: %w" , err )
506+ 	}
507+ 	for  _ , pipelinePath  :=  range  pipelinePaths  {
508+ 		dataStreamPipelineName  :=  strings .TrimSuffix (filepath .Base (pipelinePath ), filepath .Ext (pipelinePath ))
509+ 		if  strings .HasSuffix (pipelineFileName , fmt .Sprintf ("-%s-%s.yml" , manifest .Version , dataStreamPipelineName )) {
510+ 			return  rendered .Bytes (), definition , nil 
511+ 		}
512+ 	}
513+ 
514+ 	return  nil , TransformDefinition {}, fmt .Errorf ("destination ingest pipeline file %s not found: incorrect version used in pipeline or unknown pipeline" , pipelineFileName )
515+ }
516+ 
417517// ReadTransformsFromPackageRoot looks for transforms in the given package root. 
418518func  ReadTransformsFromPackageRoot (packageRoot  string ) ([]Transform , error ) {
419519	files , err  :=  filepath .Glob (filepath .Join (packageRoot , "elasticsearch" , "transform" , "*" , "transform.yml" ))
@@ -423,15 +523,9 @@ func ReadTransformsFromPackageRoot(packageRoot string) ([]Transform, error) {
423523
424524	var  transforms  []Transform 
425525	for  _ , file  :=  range  files  {
426- 		cfg , err  :=  yaml .NewConfigWithFile (file , ucfg .PathSep ("." ))
427- 		if  err  !=  nil  {
428- 			return  nil , fmt .Errorf ("reading file failed (path: %s): %w" , file , err )
429- 		}
430- 
431- 		var  definition  TransformDefinition 
432- 		err  =  cfg .Unpack (& definition )
526+ 		_ , definition , err  :=  ReadTransformDefinitionFile (file , packageRoot )
433527		if  err  !=  nil  {
434- 			return  nil , fmt .Errorf ("failed to parse  transform file \" %s \" : %w" , file , err )
528+ 			return  nil , fmt .Errorf ("failed reading  transform definition  file %q : %w" , file , err )
435529		}
436530
437531		transforms  =  append (transforms , Transform {
0 commit comments