internal/stack/boot.go (139 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 stack import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "time" "github.com/elastic/elastic-package/internal/builder" "github.com/elastic/elastic-package/internal/configuration/locations" "github.com/elastic/elastic-package/internal/files" "github.com/elastic/elastic-package/internal/profile" ) // baseComposeProjectName is the base name of the Docker Compose project used to boot up // Elastic Stack containers. const baseComposeProjectName = "elastic-package-stack" // DockerComposeProjectName returns the docker compose project name for a given profile. func DockerComposeProjectName(profile *profile.Profile) string { if profile.ProfileName == "default" { return baseComposeProjectName } return baseComposeProjectName + "-" + profile.ProfileName } // BootUp function boots up the Elastic stack. func BootUp(ctx context.Context, options Options) error { // Print information before starting the stack, for cases where // this is executed in the foreground, without daemon mode. config := Config{ Provider: ProviderCompose, ElasticsearchHost: "https://127.0.0.1:9200", ElasticsearchUsername: elasticsearchUsername, ElasticsearchPassword: elasticsearchPassword, KibanaHost: "https://127.0.0.1:5601", CACertFile: options.Profile.Path(CACertificateFile), } printUserConfig(options.Printer, config) buildPackagesPath, found, err := builder.FindBuildPackagesDirectory() if err != nil { return fmt.Errorf("finding build packages directory failed: %w", err) } stackPackagesDir, err := locations.NewLocationManager() if err != nil { return fmt.Errorf("locating stack packages directory failed: %w", err) } err = files.ClearDir(stackPackagesDir.PackagesDir()) if err != nil { return fmt.Errorf("clearing package contents failed: %w", err) } if found { fmt.Printf("Custom build packages directory found: %s\n", buildPackagesPath) err = copyUniquePackages(buildPackagesPath, stackPackagesDir.PackagesDir()) if err != nil { return fmt.Errorf("copying package contents failed: %w", err) } } options.Printer.Println("Local package-registry will serve packages from these sources:") options.Printer.Println("- Proxy to https://epr.elastic.co") if found { options.Printer.Printf("- Local directory %s\n", buildPackagesPath) } err = applyResources(options.Profile, options.StackVersion) if err != nil { return fmt.Errorf("creating stack files failed: %w", err) } err = dockerComposeBuild(ctx, options) if err != nil { return fmt.Errorf("building docker images failed: %w", err) } err = dockerComposeUp(ctx, options) if err != nil { // At least starting on 8.6.0, fleet-server may be reconfigured or // restarted after being healthy. If elastic-agent tries to enroll at // this moment, it fails inmediately, stopping and making `docker-compose up` // to fail too. // As a workaround, try to give another chance to docker-compose if only // elastic-agent failed. if onlyElasticAgentFailed(ctx, options) && !errors.Is(err, context.Canceled) { sleepTime := 2 * time.Second fmt.Printf("Elastic Agent failed to start, trying again in %s.\n", sleepTime) select { case <-time.After(sleepTime): err = dockerComposeUp(ctx, options) case <-ctx.Done(): err = ctx.Err() } } if err != nil { return fmt.Errorf("running docker-compose failed: %w", err) } } err = storeConfig(options.Profile, config) if err != nil { return fmt.Errorf("failed to store config: %w", err) } return nil } func onlyElasticAgentFailed(ctx context.Context, options Options) bool { status, err := Status(ctx, options) if err != nil { fmt.Printf("Failed to check status of the stack after failure: %v\n", err) return false } for _, service := range status { if strings.Contains(service.Name, "elastic-agent") { continue } if !strings.HasPrefix(service.Status, "running") { return false } } return true } // TearDown function takes down the testing stack. func TearDown(ctx context.Context, options Options) error { err := dockerComposeDown(ctx, options) if err != nil { return fmt.Errorf("stopping docker containers failed: %w", err) } return nil } func copyUniquePackages(sourcePath, destinationPath string) error { var skippedDirs []string dirEntries, err := os.ReadDir(sourcePath) if err != nil { return fmt.Errorf("can't read source dir (sourcePath: %s): %w", sourcePath, err) } for _, entry := range dirEntries { if entry.IsDir() { continue } if !strings.HasSuffix(entry.Name(), ".zip") { continue } name, versionZip, found := stringsCut(entry.Name(), "-") if !found { continue } version, _, found := stringsCut(versionZip, ".zip") if !found { continue } skippedDirs = append(skippedDirs, filepath.Join(name, version)) } return files.CopyWithSkipped(sourcePath, destinationPath, skippedDirs, []string{}) } // stringsCut has been imported from Go source code. // Link: https://github.com/golang/go/blob/master/src/strings/strings.go#L1187 // Once we bump up Go dependency, this will be replaced with runtime function. func stringsCut(s, sep string) (before, after string, found bool) { if i := strings.Index(s, sep); i >= 0 { return s[:i], s[i+len(sep):], true } return s, "", false }