internal/builder/packages.go (267 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 builder import ( "context" "errors" "fmt" "os" "path/filepath" "github.com/magefile/mage/sh" "github.com/elastic/elastic-package/internal/environment" "github.com/elastic/elastic-package/internal/files" "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/elastic-package/internal/packages" "github.com/elastic/elastic-package/internal/validation" ) const builtPackagesFolder = "packages" const licenseTextFileName = "LICENSE.txt" var repositoryLicenseEnv = environment.WithElasticPackagePrefix("REPOSITORY_LICENSE") type BuildOptions struct { PackageRoot string CreateZip bool SignPackage bool SkipValidation bool } // BuildDirectory function locates the target build directory. If the directory doesn't exist, it will create it. func BuildDirectory() (string, error) { buildDir, found, err := findBuildDirectory() if err != nil { return "", fmt.Errorf("can't locate build directory: %w", err) } if !found { buildDir, err = createBuildDirectory() if err != nil { return "", fmt.Errorf("can't create new build directory: %w", err) } } return buildDir, nil } func findBuildDirectory() (string, bool, error) { workDir, err := os.Getwd() if err != nil { return "", false, fmt.Errorf("can't locate build directory: %w", err) } dir := workDir // required for multi platform support root := fmt.Sprintf("%s%c", filepath.VolumeName(dir), os.PathSeparator) for dir != "." { path := filepath.Join(dir, "build") fileInfo, err := os.Stat(path) if err == nil && fileInfo.IsDir() { return path, true, nil } if dir == root { break } dir = filepath.Dir(dir) } return "", false, nil } // BuildPackagesDirectory function locates the target build directory for the package. func BuildPackagesDirectory(packageRoot string) (string, error) { buildDir, err := buildPackagesRootDirectory() if err != nil { return "", fmt.Errorf("can't locate build packages root directory: %w", err) } m, err := packages.ReadPackageManifestFromPackageRoot(packageRoot) if err != nil { return "", fmt.Errorf("reading package manifest failed (path: %s): %w", packageRoot, err) } return filepath.Join(buildDir, m.Name, m.Version), nil } // buildPackagesZipPath function locates the target zipped package path. func buildPackagesZipPath(packageRoot string) (string, error) { buildDir, err := buildPackagesRootDirectory() if err != nil { return "", fmt.Errorf("can't locate build packages root directory: %w", err) } m, err := packages.ReadPackageManifestFromPackageRoot(packageRoot) if err != nil { return "", fmt.Errorf("reading package manifest failed (path: %s): %w", packageRoot, err) } return ZippedBuiltPackagePath(buildDir, *m), nil } // ZippedBuiltPackagePath function returns the path to zipped built package. func ZippedBuiltPackagePath(buildDir string, m packages.PackageManifest) string { return filepath.Join(buildDir, fmt.Sprintf("%s-%s.zip", m.Name, m.Version)) } func buildPackagesRootDirectory() (string, error) { buildDir, found, err := FindBuildPackagesDirectory() if err != nil { return "", fmt.Errorf("can't locate build directory: %w", err) } if !found { buildDir, err = createBuildDirectory(builtPackagesFolder) if err != nil { return "", fmt.Errorf("can't create new build directory: %w", err) } } return buildDir, nil } // FindBuildPackagesDirectory function locates the target build directory for packages. func FindBuildPackagesDirectory() (string, bool, error) { buildDir, found, err := findBuildDirectory() if err != nil { return "", false, err } if found { path := filepath.Join(buildDir, builtPackagesFolder) fileInfo, err := os.Stat(path) if errors.Is(err, os.ErrNotExist) { return "", false, nil } if err != nil { return "", false, err } if fileInfo.IsDir() { return path, true, nil } } return "", false, nil } // BuildPackage function builds the package. func BuildPackage(ctx context.Context, options BuildOptions) (string, error) { destinationDir, err := BuildPackagesDirectory(options.PackageRoot) if err != nil { return "", fmt.Errorf("can't locate build directory: %w", err) } logger.Debugf("Build directory: %s\n", destinationDir) logger.Debugf("Clear target directory (path: %s)", destinationDir) err = files.ClearDir(destinationDir) if err != nil { return "", fmt.Errorf("clearing package contents failed: %w", err) } logger.Debugf("Copy package content (source: %s)", options.PackageRoot) err = files.CopyWithoutDev(options.PackageRoot, destinationDir) if err != nil { return "", fmt.Errorf("copying package contents failed: %w", err) } logger.Debug("Copy license file if needed") err = copyLicenseTextFile(filepath.Join(destinationDir, licenseTextFileName)) if err != nil { return "", fmt.Errorf("copying license text file: %w", err) } logger.Debug("Encode dashboards") err = encodeDashboards(destinationDir) if err != nil { return "", fmt.Errorf("encoding dashboards failed: %w", err) } logger.Debug("Resolve external fields") err = resolveExternalFields(options.PackageRoot, destinationDir) if err != nil { return "", fmt.Errorf("resolving external fields failed: %w", err) } err = addDynamicMappings(options.PackageRoot, destinationDir) if err != nil { return "", fmt.Errorf("adding dynamic mappings: %w", err) } if options.CreateZip { return buildZippedPackage(ctx, options, destinationDir) } if options.SkipValidation { logger.Debug("Skip validation of the built package") return destinationDir, nil } logger.Debugf("Validating built package (path: %s)", destinationDir) errs, skipped := validation.ValidateAndFilterFromPath(destinationDir) if skipped != nil { logger.Infof("Skipped errors: %v", skipped) } if errs != nil { return "", fmt.Errorf("invalid content found in built package: %w", errs) } return destinationDir, nil } func buildZippedPackage(ctx context.Context, options BuildOptions, destinationDir string) (string, error) { logger.Debug("Build zipped package") zippedPackagePath, err := buildPackagesZipPath(options.PackageRoot) if err != nil { return "", fmt.Errorf("can't evaluate path for the zipped package: %w", err) } err = files.Zip(ctx, destinationDir, zippedPackagePath) if err != nil { return "", fmt.Errorf("can't compress the built package (compressed file path: %s): %w", zippedPackagePath, err) } if options.SkipValidation { logger.Debug("Skip validation of the built .zip package") } else { logger.Debugf("Validating built .zip package (path: %s)", zippedPackagePath) errs, skipped := validation.ValidateAndFilterFromZip(zippedPackagePath) if skipped != nil { logger.Infof("Skipped errors: %v", skipped) } if errs != nil { return "", fmt.Errorf("invalid content found in built zip package: %w", errs) } } if options.SignPackage { err := signZippedPackage(options, zippedPackagePath) if err != nil { return "", err } } return zippedPackagePath, nil } func signZippedPackage(options BuildOptions, zippedPackagePath string) error { logger.Debug("Sign the package") m, err := packages.ReadPackageManifestFromPackageRoot(options.PackageRoot) if err != nil { return fmt.Errorf("reading package manifest failed (path: %s): %w", options.PackageRoot, err) } err = files.Sign(zippedPackagePath, files.SignOptions{ PackageName: m.Name, PackageVersion: m.Version, }) if err != nil { return fmt.Errorf("can't sign the zipped package (path: %s): %w", zippedPackagePath, err) } return nil } func copyLicenseTextFile(licensePath string) error { _, err := os.Stat(licensePath) if err == nil { logger.Debug("License file in the package will be used") return nil } repositoryLicenseTextFileName, userDefined := os.LookupEnv(repositoryLicenseEnv) if !userDefined { repositoryLicenseTextFileName = licenseTextFileName } sourceLicensePath, err := findRepositoryLicense(repositoryLicenseTextFileName) if !userDefined && errors.Is(err, os.ErrNotExist) { logger.Debug("No license text file is included in package") return nil } if err != nil { return fmt.Errorf("failure while looking for license %q in repository: %w", repositoryLicenseTextFileName, err) } logger.Infof("License text found in %q will be included in package", sourceLicensePath) err = sh.Copy(licensePath, sourceLicensePath) if err != nil { return fmt.Errorf("can't copy license from repository: %w", err) } return nil } func createBuildDirectory(dirs ...string) (string, error) { dir, err := files.FindRepositoryRootDirectory() if errors.Is(err, os.ErrNotExist) { return "", errors.New("package can be only built inside of a Git repository (.git folder is used as reference point)") } if err != nil { return "", err } p := []string{dir, "build"} if len(dirs) > 0 { p = append(p, dirs...) } buildDir := filepath.Join(p...) err = os.MkdirAll(buildDir, 0755) if err != nil { return "", fmt.Errorf("mkdir failed (path: %s): %w", buildDir, err) } return buildDir, nil } func findRepositoryLicense(licenseTextFileName string) (string, error) { dir, err := files.FindRepositoryRootDirectory() if err != nil { return "", err } sourceFileName := filepath.Join(dir, licenseTextFileName) _, err = os.Stat(sourceFileName) if err != nil { return "", fmt.Errorf("failed to find repository license: %w", err) } return sourceFileName, nil }