dev/import-beats/packages.go (409 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 main
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v3"
"github.com/elastic/package-registry/packages"
)
var ignoredModules = map[string]bool{"apache2": true}
type packageContent struct {
manifest packages.Package
dataStreams dataStreamContentArray
images []imageContent
kibana kibanaContent
docs []docContent
policyTemplate policyTemplateContent
}
func newPackageContent(name string) packageContent {
return packageContent{
manifest: packages.Package{
FormatVersion: "1.0.0",
BasePackage: packages.BasePackage{
Name: name,
Version: "0.0.1", // TODO
Type: "integration",
Release: "experimental",
Owner: &packages.Owner{
Github: "elastic/integrations",
},
},
License: "basic",
},
kibana: kibanaContent{
files: map[string]map[string][]byte{},
},
}
}
func (pc *packageContent) addDataStreams(ds []dataStreamContent) {
for _, dc := range ds {
for i, v := range pc.dataStreams {
if v.name == dc.name {
if v.beatType != dc.beatType {
pc.dataStreams[i].name = fmt.Sprintf("%s_%s", pc.dataStreams[i].name, pc.dataStreams[i].beatType)
dc.name = fmt.Sprintf("%s_%s", dc.name, dc.beatType)
pc.dataStreams = append(pc.dataStreams, dc)
} else {
log.Printf("Resolve naming conflict (packageName: %s, beatType: %s)", dc.name, dc.beatType)
pc.dataStreams[i] = dc
}
break
}
}
pc.dataStreams = append(pc.dataStreams, dc)
}
}
func (pc *packageContent) addKibanaContent(kc kibanaContent) {
if kc.files != nil {
for objectType, objects := range kc.files {
if _, ok := pc.kibana.files[objectType]; !ok {
pc.kibana.files[objectType] = map[string][]byte{}
}
for k, v := range objects {
pc.kibana.files[objectType][k] = v
}
}
}
}
type packageRepository struct {
iconRepository *iconRepository
kibanaMigrator *kibanaMigrator
ecsFields fieldDefinitionArray
selectedPackageNames []string
packages map[string]packageContent
}
func newPackageRepository(iconRepository *iconRepository, kibanaMigrator *kibanaMigrator,
ecsFields fieldDefinitionArray, selectedPackageNames []string,
) *packageRepository {
return &packageRepository{
iconRepository: iconRepository,
kibanaMigrator: kibanaMigrator,
ecsFields: ecsFields,
selectedPackageNames: selectedPackageNames,
packages: map[string]packageContent{},
}
}
func (r *packageRepository) createPackagesFromSource(beatsDir, beatName, beatType string) error {
beatPath := filepath.Join(beatsDir, beatName)
beatModulesPath := filepath.Join(beatPath, "module")
moduleDirs, err := ioutil.ReadDir(beatModulesPath)
if err != nil {
return errors.Wrapf(err, "cannot read directory '%s'", beatModulesPath)
}
for _, moduleDir := range moduleDirs {
if !moduleDir.IsDir() {
continue
}
moduleName := moduleDir.Name()
if !r.packageSelected(moduleName) {
continue
}
log.Printf("%s %s: module found\n", beatName, moduleName)
if _, ok := ignoredModules[moduleName]; ok {
log.Printf("%s %s: module skipped\n", beatName, moduleName)
continue
}
modulePath := path.Join(beatModulesPath, moduleName)
_, ok := r.packages[moduleName]
if !ok {
r.packages[moduleName] = newPackageContent(moduleName)
}
aPackage := r.packages[moduleName]
manifest := aPackage.manifest
// fields
moduleFields, maybeTitle, err := loadModuleFields(modulePath)
if err != nil {
return err
}
moduleFields, filteredEcsModuleFieldNames, err := filterMigratedFields(moduleFields, r.ecsFields.names())
if err != nil {
return err
}
// title
if maybeTitle != "" {
manifest.Title = &maybeTitle
manifest.Description = maybeTitle + " Integration"
}
// img
beatDocsPath := selectDocsPath(beatsDir, beatName)
images, err := createImages(beatDocsPath, modulePath)
if err != nil {
return err
}
aPackage.images = append(aPackage.images, images...)
// img/icons
// The condition prevents from adding an icon multiple times (e.g. for metricbeat and filebeat).
if len(manifest.Icons) == 0 {
icons, err := createIcons(r.iconRepository, moduleName)
if err != nil {
return err
}
aPackage.images = append(aPackage.images, icons...)
manifestIcons, err := icons.toManifestImages()
if err != nil {
return err
}
manifest.Icons = append(manifest.Icons, manifestIcons...)
}
// img/screenshots
screenshots, err := images.toManifestImages()
if err != nil {
return err
}
manifest.Screenshots = append(manifest.Screenshots, screenshots...)
// docs
if len(aPackage.docs) == 0 {
packageDocsPath := filepath.Join("packages", moduleDir.Name(), "_dev", "build", "docs")
docs, err := createDocTemplates(packageDocsPath)
if err != nil {
return err
}
aPackage.docs = append(aPackage.docs, docs...)
}
// dataStreams
moduleTitle := "TODO"
if manifest.Title != nil {
moduleTitle = *manifest.Title
}
dataStreams, err := createDataStreams(beatType, modulePath, moduleName, moduleTitle, moduleFields, filteredEcsModuleFieldNames, r.ecsFields)
if err != nil {
return err
}
dataStreams, inputVarsPerInputType, err := compactDataStreamVariables(dataStreams)
if err != nil {
return err
}
aPackage.addDataStreams(dataStreams)
// policyTemplates
aPackage.policyTemplate, err = updatePolicyTemplate(aPackage.policyTemplate, updatePolicyTemplateParameters{
moduleName: moduleName,
moduleTitle: moduleTitle,
packageType: beatType,
dataStreams: dataStreams,
inputVars: inputVarsPerInputType,
})
if err != nil {
return err
}
manifest.PolicyTemplates = aPackage.policyTemplate.toMetadataPolicyTemplates()
// kibana
kibana, err := createKibanaContent(r.kibanaMigrator, modulePath, moduleName, dataStreams.names())
if err != nil {
return err
}
aPackage.addKibanaContent(kibana)
manifest.Conditions = createConditions()
aPackage.manifest = manifest
r.packages[moduleDir.Name()] = aPackage
}
return nil
}
func (r *packageRepository) packageSelected(packageName string) bool {
if len(r.selectedPackageNames) == 0 {
return true
}
for _, f := range r.selectedPackageNames {
if f == packageName {
return true
}
}
return false
}
func (r *packageRepository) save(outputDir string) error {
for packageName, content := range r.packages {
manifest := content.manifest
log.Printf("%s/%s write package content\n", packageName, manifest.Version)
packagePath := filepath.Join(outputDir, packageName)
err := os.MkdirAll(packagePath, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory for module: '%s'", packagePath)
}
m, err := yaml.Marshal(content.manifest)
if err != nil {
return errors.Wrapf(err, "marshaling package manifest failed (packageName: %s)", packageName)
}
manifestFilePath := filepath.Join(packagePath, "manifest.yml")
err = ioutil.WriteFile(manifestFilePath, m, 0644)
if err != nil {
return errors.Wrapf(err, "writing manifest file failed (path: %s)", manifestFilePath)
}
// dataStream
for _, dataStream := range content.dataStreams {
dataStreamPath := filepath.Join(packagePath, "data_stream", dataStream.name)
err := os.MkdirAll(dataStreamPath, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory for dataStream: '%s'", dataStreamPath)
}
// dataStream/manifest.yml
m, err := yaml.Marshal(dataStream.manifest)
if err != nil {
return errors.Wrapf(err, "marshaling data stream manifest failed (dataStreamName: %s)", dataStream.name)
}
manifestFilePath := filepath.Join(dataStreamPath, "manifest.yml")
err = ioutil.WriteFile(manifestFilePath, m, 0644)
if err != nil {
return errors.Wrapf(err, "writing data stream manifest file failed (path: %s)", manifestFilePath)
}
// dataStream/fields
if len(dataStream.fields.files) > 0 {
dataStreamFieldsPath := filepath.Join(dataStreamPath, "fields")
err := os.MkdirAll(dataStreamFieldsPath, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory for data stream fields: '%s'", dataStreamPath)
}
for fieldsFileName, definitions := range dataStream.fields.files {
log.Printf("%s: write '%s' file\n", dataStream.name, fieldsFileName)
fieldsFilePath := filepath.Join(dataStreamFieldsPath, fieldsFileName)
var fieldsFile []byte
stripped := definitions.stripped()
fieldsFile, err := yaml.Marshal(&stripped)
if err != nil {
return errors.Wrapf(err, "marshalling fields file failed (path: %s)", fieldsFilePath)
}
err = ioutil.WriteFile(fieldsFilePath, fieldsFile, 0644)
if err != nil {
return errors.Wrapf(err, "writing fields file failed (path: %s)", fieldsFilePath)
}
}
}
// dataStream/elasticsearch
if len(dataStream.elasticsearch.ingestPipelines) > 0 {
ingestPipelinesPath := filepath.Join(dataStreamPath, "elasticsearch", packages.DirIngestPipeline)
err := os.MkdirAll(ingestPipelinesPath, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory for data stream ingest pipelines: '%s'", ingestPipelinesPath)
}
for _, ingestPipeline := range dataStream.elasticsearch.ingestPipelines {
ingestPipelinePath := filepath.Join(ingestPipelinesPath, ingestPipeline.targetFileName)
log.Printf("write ingest pipeline file '%s'", ingestPipelinePath)
err := ioutil.WriteFile(ingestPipelinePath, ingestPipeline.body, 0644)
if err != nil {
return errors.Wrapf(err, "writing ingest pipeline failed")
}
}
}
// dataStream/agent/stream
if len(dataStream.agent.streams) > 0 {
agentStreamPath := filepath.Join(dataStreamPath, "agent", "stream")
err := os.MkdirAll(agentStreamPath, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory for data stream agent stream: '%s'", agentStreamPath)
}
for _, agentStream := range dataStream.agent.streams {
err := ioutil.WriteFile(path.Join(agentStreamPath, agentStream.targetFileName), agentStream.body, 0644)
if err != nil {
return errors.Wrapf(err, "writing agent stream file failed")
}
}
}
}
// img
imgDstDir := path.Join(packagePath, "img")
for _, image := range content.images {
log.Printf("copy image file '%s' to '%s'", image.source, imgDstDir)
err := copyFile(image.source, imgDstDir)
if err != nil {
return errors.Wrapf(err, "copying file failed")
}
}
// kibana
if len(content.kibana.files) > 0 {
kibanaPath := filepath.Join(packagePath, "kibana")
for objectType, objects := range content.kibana.files {
resourcePath := filepath.Join(kibanaPath, objectType)
err := os.MkdirAll(resourcePath, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory for dashboard files: '%s'", resourcePath)
}
for fileName, body := range objects {
resourceFilePath := filepath.Join(resourcePath, fileName)
log.Printf("create resource file: %s", resourceFilePath)
err = ioutil.WriteFile(resourceFilePath, body, 0644)
if err != nil {
return errors.Wrapf(err, "writing resource file failed (path: %s)", resourceFilePath)
}
}
}
}
// docs
if len(content.docs) > 0 {
docsPath := filepath.Join(packagePath, "docs")
err := os.MkdirAll(docsPath, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory for docs: '%s'", docsPath)
}
for _, doc := range content.docs {
err = writeDoc(docsPath, doc, content)
if err != nil {
return errors.Wrapf(err, "cannot write docs (docsPath: %s, fileName: %s)", docsPath,
doc.fileName)
}
}
}
// changelog
changelog := newChangelog(manifest.Version)
c, err := yaml.Marshal(changelog)
if err != nil {
return errors.Wrap(err, "marshalling changelog failed")
}
header := "# newer versions go on top"
contents := strings.Replace(string(c), "entries:", header, 1) // HACK: cannot marshal sequence at root level!
c = []byte(contents)
changelogPath := filepath.Join(packagePath, "changelog.yml")
if err := ioutil.WriteFile(changelogPath, c, 0644); err != nil {
return errors.Wrap(err, "writing changelog file failed")
}
}
return nil
}
func writeDoc(docsPath string, doc docContent, aPackage packageContent) error {
log.Printf("write '%s' file\n", doc.fileName)
docFilePath := filepath.Join(docsPath, doc.fileName)
f, err := os.OpenFile(docFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer f.Close()
if err != nil {
return errors.Wrapf(err, "opening doc file failed (path: %s)", docFilePath)
}
t := template.New(doc.fileName)
if doc.templatePath == "" {
t = template.Must(t.Parse("TODO"))
} else {
t, err = t.Funcs(template.FuncMap{
"event": func(dataStream string) (string, error) {
return "TODO", nil
},
"fields": func(dataStream string) (string, error) {
return renderExportedFields(dataStream, aPackage.dataStreams)
},
}).ParseFiles(doc.templatePath)
if err != nil {
return errors.Wrapf(err, "parsing doc template failed (path: %s)", doc.templatePath)
}
}
err = t.Execute(f, nil)
if err != nil {
return errors.Wrapf(err, "rendering doc file failed (path: %s)", docFilePath)
}
return nil
}
func copyFile(src, dstDir string) error {
i := strings.LastIndex(src, "/")
sourceFileName := src[i:]
return copyFileToTarget(src, dstDir, sourceFileName)
}
func copyFileToTarget(src, dstDir, targetFileName string) error {
sourceFile, err := os.Open(src)
if err != nil {
return errors.Wrapf(err, "opening file failed (src: %s)", src)
}
defer sourceFile.Close()
dst := path.Join(dstDir, targetFileName)
err = os.MkdirAll(dstDir, 0755)
if err != nil {
return errors.Wrapf(err, "cannot make directory: '%s'", dst)
}
dstFile, err := os.Create(dst)
if err != nil {
return errors.Wrapf(err, "creating target file failed (dst: %s)", dst)
}
defer dstFile.Close()
_, err = io.Copy(dstFile, sourceFile)
if err != nil {
return errors.Wrapf(err, "copying file failed (src: %s, dst: %s)", src, dst)
}
return nil
}
func selectDocsPath(beatsDir, beatName string) string {
if strings.HasPrefix(beatName, "x-pack/") {
return path.Join(beatsDir, beatName[7:], "docs")
}
return path.Join(beatsDir, beatName, "docs")
}