Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b40cd78
Initial code, and removal of reset credentials
jsoriano Dec 24, 2024
47532c3
Assume 410 status gone is ok for elasticsearch
jsoriano Dec 24, 2024
b9e112f
Refactor client tests so they don't try to use the configured client …
jsoriano Dec 24, 2024
a44469d
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Dec 26, 2024
cd980a6
Refactor shellinit
jsoriano Dec 26, 2024
5b41cd9
Use API key in stack clients
jsoriano Dec 26, 2024
12aaebe
Ignore errors when getting logs from a non-local elasticsearch
jsoriano Dec 26, 2024
cce94bd
Share logic to start local services
jsoriano Dec 26, 2024
b3b1e76
Fix spaces in logstash config
jsoriano Dec 27, 2024
3797d20
Prepare interfaces to create policies and getting enrollment tokens
jsoriano Dec 27, 2024
04e22d2
Initial enrollment works
jsoriano Dec 27, 2024
8f17940
Tear down
jsoriano Dec 27, 2024
83beb64
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Dec 30, 2024
290c6d9
Fix tear down
jsoriano Dec 30, 2024
be6dd46
Fix system tests
jsoriano Dec 30, 2024
6169e15
Get kibana host directly from the config?
jsoriano Dec 30, 2024
2e12e02
Fix stack up with logstash
jsoriano Dec 30, 2024
f8d1cee
Fix logstash with api keys
jsoriano Dec 30, 2024
9a24380
Better idempotence
jsoriano Dec 30, 2024
c4822eb
Remove unused variable
jsoriano Dec 30, 2024
7295a2e
Revert change in initialization of kibana host
jsoriano Dec 30, 2024
0ec34f2
Implement status for environment provider
jsoriano Dec 31, 2024
5f000c5
Try to support local Fleet Server for remote stacks
jsoriano Jan 2, 2025
0a188b4
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 2, 2025
184209e
Fix certifictes on agent deployer
jsoriano Jan 3, 2025
d4d32ac
Fix fleet status when fleet server is locally managed
jsoriano Jan 3, 2025
038549c
Reuse existing fleet server hosts
jsoriano Jan 3, 2025
91f2b2d
Add options for API key in clients
jsoriano Jan 3, 2025
b854ca9
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 3, 2025
0d1a1b2
Merge branch 'api-key-clients' into api-key-support
jsoriano Jan 3, 2025
74f2049
Add host.docker.internal to the local services
jsoriano Jan 3, 2025
bbbc671
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 7, 2025
0095a32
Polish status
jsoriano Jan 7, 2025
f60e15d
Add output id to stack config
jsoriano Jan 7, 2025
0c407a0
Fix error formatting value
jsoriano Jan 7, 2025
f53325d
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 8, 2025
dcc5e0b
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 13, 2025
c65452b
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 14, 2025
ffeb24c
Remove unused API keys
jsoriano Jan 15, 2025
1079df7
Fix issues after merge
jsoriano Jan 15, 2025
699623e
Fix kubernetes agent deployer
jsoriano Jan 17, 2025
699cb0f
Add tech preview warning
jsoriano Jan 17, 2025
52ec637
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 17, 2025
aa71071
Merge remote-tracking branch 'origin/main' into api-key-support
jsoriano Jan 20, 2025
d728838
Pass context to call to get enrollment tokens
jsoriano Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Share logic to start local services
  • Loading branch information
jsoriano committed Dec 26, 2024
commit cce94bd4630a452365cb008f9bde785576f92c23
9 changes: 5 additions & 4 deletions internal/agentdeployer/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,11 @@ func (d *DockerComposeAgentDeployer) installDockerCompose(agentInfo AgentInfo) (
fleetURL := "https://fleet-server:8220"
kibanaHost := "https://kibana:5601"
stackVersion := d.stackVersion
if config.Provider == stack.ProviderServerless {
fleetURL = config.Parameters[stack.ParamServerlessFleetURL]
kibanaHost = config.KibanaHost
stackVersion = config.Parameters[stack.ParamServerlessLocalStackVersion]
if url, ok := config.Parameters[stack.ParamServerlessFleetURL]; ok {
fleetURL = url
}
if version, ok := config.Parameters[stack.ParamServerlessLocalStackVersion]; ok {
stackVersion = version
}

agentImage, err := selectElasticAgentImage(stackVersion, agentInfo.Agent.BaseImage)
Expand Down
5 changes: 5 additions & 0 deletions internal/stack/_static/elastic-agent.env.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ FLEET_ENROLL=1
FLEET_URL={{ fact "fleet_url" }}
KIBANA_FLEET_HOST={{ fact "kibana_host" }}
KIBANA_HOST={{ fact "kibana_host" }}
{{ $api_key := fact "api_key" }}
{{- if eq $api_key "" }}
ELASTICSEARCH_USERNAME={{ fact "username" }}
ELASTICSEARCH_PASSWORD={{ fact "password" }}
{{- else }}
ELASTICSEARCH_API_KEY={{ $api_key }}
{{- end }}
{{ if not (semverLessThan $version "8.0.0") }}
FLEET_TOKEN_POLICY_NAME=Elastic-Agent (elastic-package)
{{ end }}
9 changes: 9 additions & 0 deletions internal/stack/_static/logstash.conf.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ input {
}

{{ $elasticsearch_host := fact "elasticsearch_host" -}}
{{ $api_key := fact "api_key" -}}
filter {
elastic_integration {
remove_field => ['@version']
hosts => ["{{ $elasticsearch_host }}"]
{{- if eq $api_key "" }}
username => '{{ fact "username" }}'
password => '{{ fact "password" }}'
{{- else }}
api_key => '{{ $api_key }}'
{{- end }}
ssl_enabled => true
ssl_verification_mode => "none"
}
Expand All @@ -24,8 +29,12 @@ output {
if [@metadata][_ingest_document][id] {
elasticsearch {
hosts => ["{{ $elasticsearch_host }}"]
{{- if eq $api_key "" -}}
user => '{{ fact "username" }}'
password => '{{ fact "password" }}'
{{- else -}}
api_key => '{{ $api_key }}'
{{- end }}
ssl_enabled => true
{{- if eq $elasticsearch_host "https://elasticsearch:9200" }}
ssl_certificate_authorities => "/usr/share/logstash/config/certs/ca-cert.pem"
Expand Down
4 changes: 3 additions & 1 deletion internal/stack/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ var tlsServices = []tlsService{
{Name: "elastic-agent", IsClient: true},
}

var tlsServicesServerless = []tlsService{
// tlsLocalServices is the list of server TLS certificates that will
// be created for local services when the stack is not local.
var tlsLocalServices = []tlsService{
{Name: "logstash"},
{Name: "elastic-agent", IsClient: true},
}
Expand Down
2 changes: 1 addition & 1 deletion internal/stack/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestTLSCertsInitialization(t *testing.T) {
services []tlsService
}{
{"tlsServices", tlsServices},
{"tlsServicesServerless", tlsServicesServerless},
{"tlsServicesServerless", tlsLocalServices},
}
profilePath := t.TempDir()
caCertFile := filepath.Join(profilePath, "certs", "ca-cert.pem")
Expand Down
2 changes: 1 addition & 1 deletion internal/stack/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func dumpStackLogs(ctx context.Context, options DumpOptions) ([]DumpResult, erro
return nil, fmt.Errorf("can't remove output location: %w", err)
}

services, err := localServiceNames(DockerComposeProjectName(options.Profile))
services, err := localServiceNames(options.Profile)
if err != nil {
return nil, fmt.Errorf("failed to get local services: %w", err)
}
Expand Down
128 changes: 128 additions & 0 deletions internal/stack/environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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"
"fmt"
"os"

"github.com/elastic/elastic-package/internal/elasticsearch"
"github.com/elastic/elastic-package/internal/kibana"
"github.com/elastic/elastic-package/internal/profile"
)

type environmentProvider struct {
kibana *kibana.Client
elasticsearch *elasticsearch.Client
}

func newEnvironmentProvider(profile *profile.Profile) (*environmentProvider, error) {
return &environmentProvider{}, nil
}

// BootUp configures the profile to use as stack the one indicated using environment variables.
func (p *environmentProvider) BootUp(ctx context.Context, options Options) error {
config := Config{
Provider: ProviderEnvironment,
ElasticsearchAPIKey: os.Getenv(ElasticsearchAPIKeyEnv),
ElasticsearchHost: os.Getenv(ElasticsearchHostEnv),
ElasticsearchUsername: os.Getenv(ElasticsearchUsernameEnv),
ElasticsearchPassword: os.Getenv(ElasticsearchPasswordEnv),
KibanaHost: os.Getenv(KibanaHostEnv),
CACertFile: os.Getenv(CACertificateEnv),

Parameters: make(map[string]string),
}
if err := requiredEnv(config.ElasticsearchHost, ElasticsearchHostEnv); err != nil {
return err
}
if err := requiredEnv(config.KibanaHost, KibanaHostEnv); err != nil {
return err
}

err := p.initClients()
if err != nil {
return err
}
// TODO: Migrate from serverless variables.
config.Parameters[ParamServerlessFleetURL], err = p.kibana.DefaultFleetServerURL(ctx)
if err != nil {
return fmt.Errorf("cannot discover default fleet server URL: %w", err)
}

localServices := &localServicesManager{
profile: options.Profile,
}
err = localServices.start(ctx, options, config)
if err != nil {
return fmt.Errorf("failed to start local services: %w", err)
}

err = storeConfig(options.Profile, config)
if err != nil {
return fmt.Errorf("failed to store config: %w", err)
}

return nil
}

func requiredEnv(value string, envVarName string) error {
if value == "" {
return fmt.Errorf("environment variable %s required", envVarName)
}
return nil
}

func (p *environmentProvider) initClients() error {
kibana, err := NewKibanaClient()
if err != nil {
return fmt.Errorf("cannot create Kibana client: %w", err)
}
p.kibana = kibana

elasticsearch, err := NewElasticsearchClient()
if err != nil {
return fmt.Errorf("cannot create Elasticsearch client: %w", err)
}
p.elasticsearch = elasticsearch

return nil
}

// TearDown stops and/or removes a stack.
func (p *environmentProvider) TearDown(ctx context.Context, options Options) error {
localServices := &localServicesManager{
profile: options.Profile,
}
err := localServices.destroy(ctx)
if err != nil {
return fmt.Errorf("failed ot destroy local services: %v", err)
}
return nil
}

// Update updates resources associated to a stack.
func (p *environmentProvider) Update(context.Context, Options) error {
return fmt.Errorf("not implemented")
}

// Dump dumps data for debug purpouses.
func (p *environmentProvider) Dump(ctx context.Context, options DumpOptions) ([]DumpResult, error) {
for _, service := range options.Services {
if service != "elastic-agent" {
return nil, &ErrNotImplemented{
Operation: fmt.Sprintf("logs dump for service %s", service),
Provider: ProviderServerless,
}
}
}
return Dump(ctx, options)
}

// Status obtains status information of the stack.
func (p *environmentProvider) Status(context.Context, Options) ([]ServiceStatus, error) {
return nil, fmt.Errorf("not implemented")
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import (
)

var (
serverlessStackResources = []resource.Resource{
localStackResources = []resource.Resource{
&resource.File{
Path: ComposeFile,
Content: staticSource.Template("_static/serverless-docker-compose.yml.tmpl"),
Content: staticSource.Template("_static/local-services-docker-compose.yml.tmpl"),
},
&resource.File{
Path: ElasticAgentEnvFile,
Expand All @@ -31,7 +31,9 @@ var (
}
)

func applyServerlessResources(profile *profile.Profile, stackVersion string, config Config) error {
// applyLocalResources creates the local resources needed to run system tests when the stack
// is not local.
func applyLocalResources(profile *profile.Profile, stackVersion string, config Config) error {
appConfig, err := install.Configuration(install.OptionWithStackVersion(stackVersion))
if err != nil {
return fmt.Errorf("can't read application configuration: %w", err)
Expand All @@ -46,6 +48,7 @@ func applyServerlessResources(profile *profile.Profile, stackVersion string, con
"agent_image": imageRefs.ElasticAgent,
"logstash_image": imageRefs.Logstash,
"elasticsearch_host": esHostWithPort(config.ElasticsearchHost),
"api_key": config.ElasticsearchAPIKey, // TODO: !! We will need to enroll with an enrollment token?
"username": config.ElasticsearchUsername,
"password": config.ElasticsearchPassword,
"kibana_host": config.KibanaHost,
Expand All @@ -58,13 +61,13 @@ func applyServerlessResources(profile *profile.Profile, stackVersion string, con
Prefix: stackDir,
})

resources := append([]resource.Resource{}, serverlessStackResources...)
resources := append([]resource.Resource{}, localStackResources...)

// Keeping certificates in the profile directory for backwards compatibility reasons.
resourceManager.RegisterProvider("certs", &resource.FileProvider{
Prefix: profile.ProfilePath,
})
certResources, err := initTLSCertificates("certs", profile.ProfilePath, tlsServicesServerless)
certResources, err := initTLSCertificates("certs", profile.ProfilePath, tlsLocalServices)
if err != nil {
return fmt.Errorf("failed to create TLS files: %w", err)
}
Expand Down
117 changes: 117 additions & 0 deletions internal/stack/localservices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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"
"strings"

"github.com/elastic/elastic-package/internal/compose"
"github.com/elastic/elastic-package/internal/docker"
"github.com/elastic/elastic-package/internal/profile"
)

type localServicesManager struct {
profile *profile.Profile
}

func (m *localServicesManager) start(ctx context.Context, options Options, config Config) error {
err := applyLocalResources(m.profile, options.StackVersion, config)
if err != nil {
return fmt.Errorf("could not initialize compose files for local services: %w", err)
}

project, err := m.composeProject()
if err != nil {
return fmt.Errorf("could not initialize local services compose project")
}

opts := compose.CommandOptions{
ExtraArgs: []string{},
}
err = project.Build(ctx, opts)
if err != nil {
return fmt.Errorf("failed to build images for local services: %w", err)
}

if options.DaemonMode {
opts.ExtraArgs = append(opts.ExtraArgs, "-d")
}
if err := project.Up(ctx, opts); 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) {
fmt.Println("Elastic Agent failed to start, trying again.")
if err := project.Up(ctx, opts); err != nil {
return fmt.Errorf("failed to start local services: %w", err)
}
}
}

return nil
}

func (m *localServicesManager) destroy(ctx context.Context) error {
project, err := m.composeProject()
if err != nil {
return fmt.Errorf("could not initialize local services compose project")
}

opts := compose.CommandOptions{
// Remove associated volumes.
ExtraArgs: []string{"--volumes", "--remove-orphans"},
}
err = project.Down(ctx, opts)
if err != nil {
return fmt.Errorf("failed to destroy local services: %w", err)
}

return nil
}

func (m *localServicesManager) visitDescriptions(serviceFunc func(docker.ContainerDescription) error) error {
// query directly to docker to avoid load environment variables (e.g. STACK_VERSION_VARIANT) and profiles
project := m.composeProjectName()
containerIDs, err := docker.ContainerIDsWithLabel(projectLabelDockerCompose, project)
if err != nil {
return err
}

if len(containerIDs) == 0 {
return nil
}

containerDescriptions, err := docker.InspectContainers(containerIDs...)
if err != nil {
return err
}

for _, containerDescription := range containerDescriptions {
serviceName := containerDescription.Config.Labels.ComposeService
if strings.HasSuffix(serviceName, readyServicesSuffix) {
continue
}
err := serviceFunc(containerDescription)
if err != nil {
return err
}
}
return nil
}

func (m *localServicesManager) composeProject() (*compose.Project, error) {
composeFile := m.profile.Path(ProfileStackPath, ComposeFile)
return compose.NewProject(m.composeProjectName(), composeFile)
}

func (m *localServicesManager) composeProjectName() string {
return DockerComposeProjectName(m.profile)
}
Loading