Skip to content

Commit b6b74e5

Browse files
authored
New stack provider for environment variables (#2298)
Add a new stack provider that can be used when elastic-package is configured with environment variables. This provider fills the gaps for using elastic-package for testing on clusters that are not originally managed by elastic-package. To use it, at least ELASTIC_PACKAGE_KIBANA_HOST and ELASTIC_PACKAGE_ELASTICSEARCH_HOST environment variables need to be set, other variables can be used to configure authentication, SSL and so on. Then, run `elastic-package stack up --provider environment`. This provider manages: - Local elastic-agent and initial agent policy. - Optional local logstash and logstash output. - If no Fleet Server is available, it configures the host, a Fleet Server policy, a new service token and starts a local Fleet Server. Most commands should work if the provider is able to setup everything.
1 parent c975508 commit b6b74e5

26 files changed

+1051
-145
lines changed

internal/agentdeployer/_static/docker-agent-base.yml.tmpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{{- $dockerfile_hash := fact "dockerfile_hash" -}}
66
{{- $stack_version := fact "stack_version" }}
77
{{- $agent_image := fact "agent_image" }}
8+
{{- $enrollment_token := fact "enrollment_token" }}
89
services:
910
elastic-agent:
1011
hostname: ${AGENT_HOSTNAME}
@@ -40,9 +41,13 @@ services:
4041
- FLEET_ENROLL=1
4142
- FLEET_URL={{ fact "fleet_url" }}
4243
- KIBANA_HOST={{ fact "kibana_host" }}
44+
{{ if eq $enrollment_token "" }}
4345
- FLEET_TOKEN_POLICY_NAME=${FLEET_TOKEN_POLICY_NAME}
4446
- ELASTICSEARCH_USERNAME={{ fact "elasticsearch_username" }}
4547
- ELASTICSEARCH_PASSWORD={{ fact "elasticsearch_password" }}
48+
{{ else }}
49+
- FLEET_ENROLLMENT_TOKEN={{ $enrollment_token }}
50+
{{ end }}
4651
volumes:
4752
- type: bind
4853
source: ${LOCAL_CA_CERT}
@@ -57,3 +62,5 @@ services:
5762
source: ${SERVICE_LOGS_DIR}
5863
target: /run/service_logs/
5964
read_only: false
65+
extra_hosts:
66+
- "host.docker.internal:host-gateway"

internal/agentdeployer/_static/elastic-agent-managed.yaml.tmpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ spec:
4444
value: {{ .fleetURL }}
4545
# If left empty KIBANA_HOST, KIBANA_FLEET_USERNAME, KIBANA_FLEET_PASSWORD are needed
4646
- name: FLEET_ENROLLMENT_TOKEN
47-
value: ""
47+
value: "{{ .enrollmentToken }}"
4848
- name: FLEET_TOKEN_POLICY_NAME
4949
value: "{{ .elasticAgentTokenPolicyName }}"
5050
- name: KIBANA_HOST
5151
value: {{ .kibanaURL }}
5252
- name: KIBANA_FLEET_USERNAME
53-
value: "elastic"
53+
value: {{ .username }}
5454
- name: KIBANA_FLEET_PASSWORD
55-
value: "changeme"
55+
value: {{ .password }}
5656
- name: SSL_CERT_DIR
5757
value: "/etc/ssl/certs:/etc/ssl/elastic-package"
5858
- name: NODE_NAME

internal/agentdeployer/agent.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func (d *DockerComposeAgentDeployer) SetUp(ctx context.Context, agentInfo AgentI
119119
fmt.Sprintf("%s=%s", agentHostnameEnv, d.agentHostname()),
120120
)
121121

122-
configDir, err := d.installDockerCompose(agentInfo)
122+
configDir, err := d.installDockerCompose(ctx, agentInfo)
123123
if err != nil {
124124
return nil, fmt.Errorf("could not create resources for custom agent: %w", err)
125125
}
@@ -233,7 +233,7 @@ func (d *DockerComposeAgentDeployer) agentName() string {
233233

234234
// installDockerCompose creates the files needed to run the custom elastic agent and returns
235235
// the directory with these files.
236-
func (d *DockerComposeAgentDeployer) installDockerCompose(agentInfo AgentInfo) (string, error) {
236+
func (d *DockerComposeAgentDeployer) installDockerCompose(ctx context.Context, agentInfo AgentInfo) (string, error) {
237237
customAgentDir, err := CreateDeployerDir(d.profile, fmt.Sprintf("docker-agent-%s-%s", d.agentName(), d.agentRunID))
238238
if err != nil {
239239
return "", fmt.Errorf("failed to create directory for custom agent files: %w", err)
@@ -254,14 +254,31 @@ func (d *DockerComposeAgentDeployer) installDockerCompose(agentInfo AgentInfo) (
254254
if err != nil {
255255
return "", fmt.Errorf("failed to load config from profile: %w", err)
256256
}
257+
enrollmentToken := ""
258+
if config.ElasticsearchAPIKey != "" {
259+
// TODO: Review if this is the correct place to get the enrollment token.
260+
kibanaClient, err := stack.NewKibanaClientFromProfile(d.profile)
261+
if err != nil {
262+
return "", fmt.Errorf("failed to create kibana client: %w", err)
263+
}
264+
enrollmentToken, err = kibanaClient.GetEnrollmentTokenForPolicyID(ctx, agentInfo.Policy.ID)
265+
if err != nil {
266+
return "", fmt.Errorf("failed to get enrollment token for policy %q: %w", agentInfo.Policy.Name, err)
267+
}
268+
}
257269

270+
// TODO: Include these settings more explicitly in `config`.
258271
fleetURL := "https://fleet-server:8220"
259272
kibanaHost := "https://kibana:5601"
260273
stackVersion := d.stackVersion
261-
if config.Provider == stack.ProviderServerless {
262-
fleetURL = config.Parameters[stack.ParamServerlessFleetURL]
274+
if config.Provider != stack.ProviderCompose {
263275
kibanaHost = config.KibanaHost
264-
stackVersion = config.Parameters[stack.ParamServerlessLocalStackVersion]
276+
}
277+
if url, ok := config.Parameters[stack.ParamServerlessFleetURL]; ok {
278+
fleetURL = url
279+
}
280+
if version, ok := config.Parameters[stack.ParamServerlessLocalStackVersion]; ok {
281+
stackVersion = version
265282
}
266283

267284
agentImage, err := selectElasticAgentImage(stackVersion, agentInfo.Agent.BaseImage)
@@ -280,9 +297,10 @@ func (d *DockerComposeAgentDeployer) installDockerCompose(agentInfo AgentInfo) (
280297
"dockerfile_hash": hex.EncodeToString(hashDockerfile),
281298
"stack_version": stackVersion,
282299
"fleet_url": fleetURL,
283-
"kibana_host": kibanaHost,
300+
"kibana_host": stack.DockerInternalHost(kibanaHost),
284301
"elasticsearch_username": config.ElasticsearchUsername,
285302
"elasticsearch_password": config.ElasticsearchPassword,
303+
"enrollment_token": enrollmentToken,
286304
})
287305

288306
resourceManager.RegisterProvider("file", &resource.FileProvider{

internal/agentdeployer/info.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,4 @@ type AgentInfo struct {
107107

108108
AgentSettings
109109
}
110-
111-
// CustomProperties store additional data used to boot up the service, e.g. AWS credentials.
112-
CustomProperties map[string]interface{}
113110
}

internal/agentdeployer/kubernetes.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ type kubernetesDeployedAgent struct {
5757
}
5858

5959
func (s kubernetesDeployedAgent) TearDown(ctx context.Context) error {
60-
elasticAgentManagedYaml, err := getElasticAgentYAML(s.profile, s.stackVersion, s.agentInfo.Policy.Name, s.agentName)
60+
elasticAgentManagedYaml, err := getElasticAgentYAML(ctx, s.profile, s.agentInfo, s.stackVersion, s.agentName)
6161
if err != nil {
6262
return fmt.Errorf("can't retrieve Kubernetes file for Elastic Agent: %w", err)
6363
}
@@ -123,7 +123,7 @@ func (ksd *KubernetesAgentDeployer) SetUp(ctx context.Context, agentInfo AgentIn
123123
if ksd.runTearDown || ksd.runTestsOnly {
124124
logger.Debug("Skip install Elastic Agent in cluster")
125125
} else {
126-
err = installElasticAgentInCluster(ctx, ksd.profile, ksd.stackVersion, agentInfo.Policy.Name, agentName)
126+
err = installElasticAgentInCluster(ctx, ksd.profile, agentInfo, ksd.stackVersion, agentName)
127127
if err != nil {
128128
return nil, fmt.Errorf("can't install Elastic-Agent in the Kubernetes cluster: %w", err)
129129
}
@@ -155,10 +155,10 @@ func (ksd *KubernetesAgentDeployer) agentName() string {
155155

156156
var _ AgentDeployer = new(KubernetesAgentDeployer)
157157

158-
func installElasticAgentInCluster(ctx context.Context, profile *profile.Profile, stackVersion, policyName, agentName string) error {
158+
func installElasticAgentInCluster(ctx context.Context, profile *profile.Profile, agentInfo AgentInfo, stackVersion, agentName string) error {
159159
logger.Debug("install Elastic Agent in the Kubernetes cluster")
160160

161-
elasticAgentManagedYaml, err := getElasticAgentYAML(profile, stackVersion, policyName, agentName)
161+
elasticAgentManagedYaml, err := getElasticAgentYAML(ctx, profile, agentInfo, stackVersion, agentName)
162162
if err != nil {
163163
return fmt.Errorf("can't retrieve Kubernetes file for Elastic Agent: %w", err)
164164
}
@@ -176,8 +176,36 @@ func installElasticAgentInCluster(ctx context.Context, profile *profile.Profile,
176176
//go:embed _static/elastic-agent-managed.yaml.tmpl
177177
var elasticAgentManagedYamlTmpl string
178178

179-
func getElasticAgentYAML(profile *profile.Profile, stackVersion, policyName, agentName string) ([]byte, error) {
179+
func getElasticAgentYAML(ctx context.Context, profile *profile.Profile, agentInfo AgentInfo, stackVersion, agentName string) ([]byte, error) {
180180
logger.Debugf("Prepare YAML definition for Elastic Agent running in stack v%s", stackVersion)
181+
config, err := stack.LoadConfig(profile)
182+
if err != nil {
183+
return nil, fmt.Errorf("failed to load config from profile: %w", err)
184+
}
185+
fleetURL := "https://fleet-server:8220"
186+
kibanaURL := "https://kibana:5601"
187+
if config.Provider != stack.ProviderCompose {
188+
kibanaURL = config.KibanaHost
189+
}
190+
if url, ok := config.Parameters[stack.ParamServerlessFleetURL]; ok {
191+
fleetURL = url
192+
}
193+
if version, ok := config.Parameters[stack.ParamServerlessLocalStackVersion]; ok {
194+
stackVersion = version
195+
}
196+
197+
enrollmentToken := ""
198+
if config.ElasticsearchAPIKey != "" {
199+
// TODO: Review if this is the correct place to get the enrollment token.
200+
kibanaClient, err := stack.NewKibanaClientFromProfile(profile)
201+
if err != nil {
202+
return nil, fmt.Errorf("failed to create kibana client: %w", err)
203+
}
204+
enrollmentToken, err = kibanaClient.GetEnrollmentTokenForPolicyID(ctx, agentInfo.Policy.ID)
205+
if err != nil {
206+
return nil, fmt.Errorf("failed to get enrollment token for policy %q: %w", agentInfo.Policy.Name, err)
207+
}
208+
}
181209

182210
appConfig, err := install.Configuration(install.OptionWithStackVersion(stackVersion))
183211
if err != nil {
@@ -193,11 +221,14 @@ func getElasticAgentYAML(profile *profile.Profile, stackVersion, policyName, age
193221

194222
var elasticAgentYaml bytes.Buffer
195223
err = tmpl.Execute(&elasticAgentYaml, map[string]string{
196-
"fleetURL": "https://fleet-server:8220",
197-
"kibanaURL": "https://kibana:5601",
224+
"fleetURL": fleetURL,
225+
"kibanaURL": kibanaURL,
226+
"username": config.ElasticsearchUsername,
227+
"password": config.ElasticsearchPassword,
228+
"enrollmentToken": enrollmentToken,
198229
"caCertPem": caCert,
199230
"elasticAgentImage": appConfig.StackImageRefs().ElasticAgent,
200-
"elasticAgentTokenPolicyName": getTokenPolicyName(stackVersion, policyName),
231+
"elasticAgentTokenPolicyName": getTokenPolicyName(stackVersion, agentInfo.Policy.Name),
201232
"agentName": agentName,
202233
})
203234
if err != nil {

internal/kubectl/kubectl_apply.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ func waitForReadyResources(resources []resource) error {
135135
// be unavailable (DaemonSet.spec.updateStrategy.rollingUpdate.maxUnavailable defaults to 1).
136136
// daemonSetReady will return true regardless of the pod not being ready yet.
137137
// Can be solved with multi-node clusters.
138+
// TODO: Support context cancelation in this wait. We rely on a helm waiter
139+
// that doesn't support it.
138140
err := kubeClient.Wait(resList, readinessTimeout)
139141
if err != nil {
140142
return fmt.Errorf("waiter failed: %w", err)

internal/serverless/project.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ type Project struct {
3131
Region string `json:"region_id"`
3232

3333
Credentials struct {
34-
Username string `json:"username"`
35-
Password string `json:"password"`
34+
Username string `json:"username,omitempty"`
35+
Password string `json:"password,omitempty"`
3636
} `json:"credentials"`
3737

3838
Endpoints struct {
@@ -150,7 +150,7 @@ func (p *Project) getFleetHealth(ctx context.Context) error {
150150

151151
if status.Status != "HEALTHY" {
152152
return fmt.Errorf("fleet status %s", status.Status)
153-
154153
}
154+
155155
return nil
156156
}

internal/stack/_static/elastic-agent.env.tmpl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ FLEET_ENROLL=1
33
FLEET_URL={{ fact "fleet_url" }}
44
KIBANA_FLEET_HOST={{ fact "kibana_host" }}
55
KIBANA_HOST={{ fact "kibana_host" }}
6+
{{- $enrollment_token := fact "enrollment_token" }}
7+
{{- if eq $enrollment_token "" }}
68
ELASTICSEARCH_USERNAME={{ fact "username" }}
79
ELASTICSEARCH_PASSWORD={{ fact "password" }}
8-
{{ if not (semverLessThan $version "8.0.0") }}
10+
{{- if not (semverLessThan $version "8.0.0") }}
911
FLEET_TOKEN_POLICY_NAME=Elastic-Agent (elastic-package)
10-
{{ end }}
12+
{{- end }}
13+
{{- else }}
14+
FLEET_ENROLLMENT_TOKEN={{ $enrollment_token }}
15+
{{- end }}

internal/stack/_static/fleet-server-healthcheck.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ NUMBER_SUCCESSES="$1"
66
WAITING_TIME="$2"
77

88
healthcheck() {
9-
curl -s --cacert /etc/ssl/elastic-agent/ca-cert.pem -f https://localhost:8220/api/status | grep -i healthy 2>&1 >/dev/null
9+
curl -s --cacert /etc/ssl/certs/elastic-package.pem -f https://localhost:8220/api/status | grep -i healthy 2>&1 >/dev/null
1010
}
1111

1212
# Fleet Server can restart after announcing to be healthy, agents connecting during this restart will
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
services:
2+
{{- $fleet_server_managed := fact "fleet_server_managed" }}
3+
{{- if eq $fleet_server_managed "true" }}
4+
{{- $fleet_healthcheck_success_checks := 3 -}}
5+
{{- $fleet_healthcheck_waiting_time := 1 -}}
6+
{{- $version := fact "agent_version" -}}
7+
{{- if semverLessThan $version "8.0.0" -}}
8+
{{- $fleet_healthcheck_success_checks = 10 -}}
9+
{{- $fleet_healthcheck_waiting_time = 2 -}}
10+
{{- end }}
11+
fleet-server:
12+
image: "{{ fact "agent_image" }}"
13+
healthcheck:
14+
test: "bash /healthcheck.sh {{ $fleet_healthcheck_success_checks }} {{ $fleet_healthcheck_waiting_time }}"
15+
start_period: 60s
16+
interval: 5s
17+
hostname: docker-fleet-server
18+
environment:
19+
- "ELASTICSEARCH_HOST={{ fact "elasticsearch_host" }}"
20+
- "FLEET_SERVER_CERT=/etc/ssl/fleet-server/cert.pem"
21+
- "FLEET_SERVER_CERT_KEY=/etc/ssl/fleet-server/key.pem"
22+
- "FLEET_SERVER_ELASTICSEARCH_HOST={{ fact "elasticsearch_host" }}"
23+
- "FLEET_SERVER_ENABLE=1"
24+
- "FLEET_SERVER_HOST=0.0.0.0"
25+
- "FLEET_SERVER_SERVICE_TOKEN={{ fact "fleet_service_token" }}"
26+
- "FLEET_SERVER_POLICY={{ fact "fleet_server_policy" }}"
27+
- "FLEET_URL={{ fact "fleet_url" }}"
28+
- "KIBANA_FLEET_HOST={{ fact "kibana_host" }}"
29+
- "KIBANA_FLEET_SERVICE_TOKEN={{ fact "fleet_service_token" }}"
30+
- "KIBANA_FLEET_SERVER_POLICY={{ fact "fleet_server_policy" }}"
31+
- "KIBANA_FLEET_SETUP=1"
32+
- "KIBANA_HOST={{ fact "kibana_host" }}"
33+
volumes:
34+
- "../certs/ca-cert.pem:/etc/ssl/certs/elastic-package.pem:ro"
35+
- "../certs/fleet-server:/etc/ssl/fleet-server:ro"
36+
- "./fleet-server-healthcheck.sh:/healthcheck.sh:ro"
37+
ports:
38+
- "127.0.0.1:8220:8220"
39+
extra_hosts:
40+
- "host.docker.internal:host-gateway"
41+
42+
fleet-server_is_ready:
43+
image: tianon/true:multiarch
44+
depends_on:
45+
fleet-server:
46+
condition: service_healthy
47+
{{- end }}
48+
49+
elastic-agent:
50+
image: "{{ fact "agent_image" }}"
51+
{{- if eq $fleet_server_managed "true" }}
52+
depends_on:
53+
fleet-server:
54+
condition: service_healthy
55+
{{- end }}
56+
healthcheck:
57+
test: "elastic-agent status"
58+
timeout: 2s
59+
start_period: 360s
60+
retries: 180
61+
interval: 5s
62+
hostname: docker-fleet-agent
63+
env_file: "./elastic-agent.env"
64+
cap_drop:
65+
- ALL
66+
volumes:
67+
- type: bind
68+
source: ../../../tmp/service_logs/
69+
target: /tmp/service_logs/
70+
# Mount service_logs under /run too as a testing workaround for the journald input (see elastic-package#1235).
71+
- type: bind
72+
source: ../../../tmp/service_logs/
73+
target: /run/service_logs/
74+
- "../certs/ca-cert.pem:/etc/ssl/certs/elastic-package.pem"
75+
extra_hosts:
76+
- "host.docker.internal:host-gateway"
77+
78+
elastic-agent_is_ready:
79+
image: tianon/true:multiarch
80+
depends_on:
81+
elastic-agent:
82+
condition: service_healthy
83+
84+
{{ $logstash_enabled := fact "logstash_enabled" }}
85+
{{ if eq $logstash_enabled "true" }}
86+
logstash:
87+
build:
88+
dockerfile: "./Dockerfile.logstash"
89+
args:
90+
IMAGE: "{{ fact "logstash_image" }}"
91+
healthcheck:
92+
test: bin/logstash -t
93+
start_period: 120s
94+
interval: 60s
95+
timeout: 60s
96+
retries: 5
97+
volumes:
98+
- "../certs/logstash:/usr/share/logstash/config/certs"
99+
ports:
100+
- "127.0.0.1:5044:5044"
101+
- "127.0.0.1:9600:9600"
102+
environment:
103+
- XPACK_MONITORING_ENABLED=false
104+
- ELASTIC_API_KEY={{ fact "api_key" }}
105+
- ELASTIC_USER={{ fact "username" }}
106+
- ELASTIC_PASSWORD={{ fact "password" }}
107+
- ELASTIC_HOSTS={{ fact "elasticsearch_host" }}
108+
extra_hosts:
109+
- "host.docker.internal:host-gateway"
110+
111+
logstash_is_ready:
112+
image: tianon/true:multiarch
113+
depends_on:
114+
logstash:
115+
condition: service_healthy
116+
{{ end }}

0 commit comments

Comments
 (0)