internal/packages/assets.go (182 lines of code) (raw):

// 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 packages import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "github.com/elastic/elastic-package/internal/multierror" ) // AssetType represents the type of package asset. type AssetType string type assetTypeFolder struct { typeName AssetType folderName string } func newAssetType(typeName AssetType) assetTypeFolder { return assetTypeFolder{ typeName: typeName, folderName: string(typeName), } } func newAssetTypeWithFolder(typeName AssetType, folderName string) assetTypeFolder { return assetTypeFolder{ typeName: typeName, folderName: folderName, } } // Supported asset types. var ( AssetTypeElasticsearchIndexTemplate = newAssetType("index_template") AssetTypeElasticsearchIngestPipeline = newAssetType("ingest_pipeline") AssetTypeKibanaSavedSearch = newAssetType("search") AssetTypeKibanaVisualization = newAssetType("visualization") AssetTypeKibanaDashboard = newAssetType("dashboard") AssetTypeKibanaMap = newAssetType("map") AssetTypeKibanaLens = newAssetType("lens") AssetTypeSecurityRule = newAssetTypeWithFolder("security-rule", "security_rule") ) // Asset represents a package asset to be loaded into Kibana or Elasticsearch. type Asset struct { ID string `json:"id"` Type AssetType `json:"type"` DataStream string SourcePath string } // String method returns a string representation of the asset func (asset Asset) String() string { return fmt.Sprintf("%s (type: %s)", asset.ID, asset.Type) } // LoadPackageAssets parses the package contents and returns a list of assets defined by the package. func LoadPackageAssets(pkgRootPath string) ([]Asset, error) { assets, err := loadKibanaAssets(pkgRootPath) if err != nil { return nil, fmt.Errorf("could not load kibana assets: %w", err) } a, err := loadElasticsearchAssets(pkgRootPath) if err != nil { return a, fmt.Errorf("could not load elasticsearch assets: %w", err) } assets = append(assets, a...) return assets, nil } func loadKibanaAssets(pkgRootPath string) ([]Asset, error) { kibanaAssetsFolderPath := filepath.Join(pkgRootPath, "kibana") var ( errs multierror.Error assetTypes = []assetTypeFolder{ AssetTypeKibanaDashboard, AssetTypeKibanaVisualization, AssetTypeKibanaSavedSearch, AssetTypeKibanaMap, AssetTypeKibanaLens, AssetTypeSecurityRule, } assets []Asset ) for _, assetType := range assetTypes { a, err := loadFileBasedAssets(kibanaAssetsFolderPath, assetType) if err != nil { errs = append(errs, fmt.Errorf("could not load kibana %s assets: %w", assetType, err)) continue } assets = append(assets, a...) } if len(errs) > 0 { return nil, errs } return assets, nil } func loadElasticsearchAssets(pkgRootPath string) ([]Asset, error) { packageManifestPath := filepath.Join(pkgRootPath, PackageManifestFile) pkgManifest, err := ReadPackageManifest(packageManifestPath) if err != nil { return nil, fmt.Errorf("reading package manifest file failed: %w", err) } dataStreamManifestPaths, err := filepath.Glob(filepath.Join(pkgRootPath, "data_stream", "*", DataStreamManifestFile)) if err != nil { return nil, fmt.Errorf("could not read data stream manifest file paths: %w", err) } var assets []Asset for _, dsManifestPath := range dataStreamManifestPaths { dsManifest, err := ReadDataStreamManifest(dsManifestPath) if err != nil { return nil, fmt.Errorf("reading data stream manifest failed: %w", err) } indexTemplateName := dsManifest.IndexTemplateName(pkgManifest.Name) asset := Asset{ ID: indexTemplateName, Type: AssetTypeElasticsearchIndexTemplate.typeName, DataStream: dsManifest.Name, } assets = append(assets, asset) if dsManifest.Type == dataStreamTypeLogs || dsManifest.Type == dataStreamTypeTraces { elasticsearchDirPath := filepath.Join(filepath.Dir(dsManifestPath), "elasticsearch", "ingest_pipeline") var pipelineFiles []string for _, pattern := range []string{"*.json", "*.yml"} { files, err := filepath.Glob(filepath.Join(elasticsearchDirPath, pattern)) if err != nil { return nil, fmt.Errorf("listing '%s' in '%s': %w", pattern, elasticsearchDirPath, err) } pipelineFiles = append(pipelineFiles, files...) } if len(pipelineFiles) == 0 { continue // ingest pipeline is not defined } // If no dataset value is set in the manifest, it falls back to {packageName}.{dirName} if dsManifest.Dataset == "" { dsManifest.Dataset = fmt.Sprintf("%s.%s", pkgManifest.Name, dsManifest.Name) } ingestPipelineName := dsManifest.GetPipelineNameOrDefault() if ingestPipelineName == defaultPipelineName { ingestPipelineName = fmt.Sprintf("%s-%s-%s", dsManifest.Type, dsManifest.Dataset, pkgManifest.Version) } asset = Asset{ ID: ingestPipelineName, Type: AssetTypeElasticsearchIngestPipeline.typeName, DataStream: dsManifest.Name, } assets = append(assets, asset) } } // No Elasticsearch asset is created when an Input package is installed through the API. return assets, nil } func loadFileBasedAssets(kibanaAssetsFolderPath string, assetType assetTypeFolder) ([]Asset, error) { assetsFolderPath := filepath.Join(kibanaAssetsFolderPath, assetType.folderName) _, err := os.Stat(assetsFolderPath) if err != nil && errors.Is(err, os.ErrNotExist) { // No assets folder defined; nothing to load return nil, nil } if err != nil { return nil, fmt.Errorf("error finding kibana %s assets folder: %w", assetType.typeName, err) } paths, err := filepath.Glob(filepath.Join(assetsFolderPath, "*.json")) if err != nil { return nil, fmt.Errorf("could not read %s files: %w", assetType.typeName, err) } var assets []Asset for _, assetPath := range paths { assetID, err := readAssetID(assetPath) if err != nil { return nil, fmt.Errorf("can't read asset ID (path: %s): %w", assetPath, err) } asset := Asset{ ID: assetID, Type: assetType.typeName, SourcePath: assetPath, } assets = append(assets, asset) } return assets, nil } func readAssetID(assetPath string) (string, error) { content, err := os.ReadFile(assetPath) if err != nil { return "", fmt.Errorf("can't read file body: %w", err) } assetBody := struct { ID string `json:"id"` }{} err = json.Unmarshal(content, &assetBody) if err != nil { return "", fmt.Errorf("can't unmarshal asset: %w", err) } if assetBody.ID == "" { return "", errors.New("empty asset ID") } return assetBody.ID, nil }