internal/packages/installer/factory.go (102 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 installer import ( "context" "errors" "fmt" "github.com/Masterminds/semver/v3" "github.com/elastic/elastic-package/internal/builder" "github.com/elastic/elastic-package/internal/kibana" "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/elastic-package/internal/packages" "github.com/elastic/elastic-package/internal/validation" ) var ( semver8_7_0 = semver.MustParse("8.7.0") semver8_8_2 = semver.MustParse("8.8.2") ) // Installer is responsible for installation/uninstallation of the package. type Installer interface { Install(context.Context) (*InstalledPackage, error) Uninstall(context.Context) error Manifest(context.Context) (*packages.PackageManifest, error) } // Options are the parameters used to build an installer. type Options struct { Kibana *kibana.Client RootPath string ZipPath string SkipValidation bool } // NewForPackage creates a new installer for a package, given its root path, or its prebuilt zip. // If a zip path is given, this package is validated and installed as is. This fails on versions // of Kibana lower than 8.7.0. // When no zip is given, package is built as zip and installed if version is at least 8.7.0, // or from the package registry otherwise. func NewForPackage(ctx context.Context, options Options) (Installer, error) { if options.Kibana == nil { return nil, errors.New("missing kibana client") } if options.RootPath == "" && options.ZipPath == "" { return nil, errors.New("missing package root path or pre-built zip package") } version, err := kibanaVersion(options.Kibana) if err != nil { return nil, fmt.Errorf("failed to get kibana version: %w", err) } supportsUploadZip, reason, err := isAllowedInstallationViaApi(context.TODO(), options.Kibana, version) if err != nil { return nil, fmt.Errorf("failed to validate whether or not it can be used upload API: %w", err) } if options.ZipPath != "" { if !supportsUploadZip { return nil, errors.New(reason) } if !options.SkipValidation { logger.Debugf("Validating built .zip package (path: %s)", options.ZipPath) errs, skipped := validation.ValidateAndFilterFromZip(options.ZipPath) if skipped != nil { logger.Infof("Skipped errors: %v", skipped) } if errs != nil { return nil, fmt.Errorf("invalid content found in built zip package: %w", errs) } } logger.Debug("Skip validation of the built .zip package") return CreateForZip(options.Kibana, options.ZipPath) } target, err := builder.BuildPackage(ctx, builder.BuildOptions{ PackageRoot: options.RootPath, CreateZip: supportsUploadZip, SignPackage: false, SkipValidation: options.SkipValidation, }) if err != nil { return nil, fmt.Errorf("failed to build package: %v", err) } if supportsUploadZip { return CreateForZip(options.Kibana, target) } return CreateForManifest(options.Kibana, target) } func isAllowedInstallationViaApi(ctx context.Context, kbnClient *kibana.Client, kibanaVersion *semver.Version) (bool, string, error) { reason := "" if kibanaVersion.LessThan(semver8_7_0) { reason = fmt.Sprintf("not supported uploading zip packages in Kibana %s (%s required)", kibanaVersion, semver8_7_0) return false, reason, nil } if kibanaVersion.LessThan(semver8_8_2) { err := kbnClient.EnsureZipPackageCanBeInstalled(ctx) if errors.Is(err, kibana.ErrNotSupported) { reason = fmt.Sprintf("not supported uploading zip packages in Kibana %s (%s required or Enteprise license)", kibanaVersion, semver8_8_2) return false, reason, nil } if err != nil { return false, "", err } } return true, "", nil } func kibanaVersion(kibana *kibana.Client) (*semver.Version, error) { version, err := kibana.Version() if err != nil { return nil, err } sv, err := semver.NewVersion(version.Number) if err != nil { return nil, fmt.Errorf("invalid kibana version: %w", err) } return sv, nil }