Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config option to deploy custom elastic-agents as test services #786

Merged
merged 30 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d515d21
Add config option to deploy custom elastic-agents as test services
marc-gr Apr 11, 2022
48f20d4
Merge remote-tracking branch 'upstream/main' into custom-agents
marc-gr Apr 11, 2022
fdf4b9f
Tidy go.mod
marc-gr Apr 11, 2022
fbc5d52
Merge remote-tracking branch 'upstream/main' into custom-agents
marc-gr May 3, 2022
07820ad
Simplify runner changes
marc-gr May 3, 2022
af8e172
Clean go.mod
marc-gr May 3, 2022
31aec19
Move logic to custom_agent deployer
marc-gr May 3, 2022
15d7df4
Use DockerComposeDeployedService code
marc-gr May 4, 2022
9bcb382
Connect to network before healthcheck
marc-gr May 4, 2022
5b649d9
Add test, docs, and initialize the same as a composer deployer
marc-gr May 4, 2022
009e259
Change Makefile
marc-gr May 5, 2022
d17bb7d
Format test package
marc-gr May 5, 2022
99131b6
Add test to CI
marc-gr May 5, 2022
632a0f5
Bump stack versions
marc-gr May 5, 2022
2bf8399
Ignore errors in test pipeline
marc-gr May 6, 2022
ca9fb47
Use apache as custom-agent test and revert stack version changes
marc-gr May 6, 2022
7b10af7
Fail if docker-compose.yml is not found
marc-gr May 6, 2022
6e108f9
Change custom agent deployer to use a base agent config
marc-gr May 12, 2022
a1cc748
Replace apache custom agent example with auditd_manager
marc-gr May 12, 2022
aa5cb6e
Merge remote-tracking branch 'upstream/main' into custom-agents
marc-gr May 12, 2022
4b63068
Wrap compose service deployer
marc-gr May 16, 2022
f89d55f
Merge remote-tracking branch 'upstream/main' into custom-agents
marc-gr May 16, 2022
c586f71
Format
marc-gr May 16, 2022
b158777
Overwrite setup and teardown to avoid changing compose deployer
marc-gr May 16, 2022
241a1b4
Use service variant to avoid overriding teardown entirely
marc-gr May 16, 2022
4af44a4
Update docs
marc-gr May 16, 2022
b0ae2ad
Revert Makefile unrelated change
marc-gr May 16, 2022
5c6f901
Use installed resources instead of a temp file
marc-gr May 17, 2022
f6ab76b
add version to custom-agent.yml
marc-gr May 17, 2022
e37de08
quote version field
marc-gr May 17, 2022
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
1 change: 1 addition & 0 deletions .ci/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pipeline {
'stack-command-8x': generateTestCommandStage(command: 'test-stack-command-8x', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*']),
'check-packages-with-kind': generateTestCommandStage(command: 'test-check-packages-with-kind', artifacts: ['build/test-results/*.xml', 'build/kubectl-dump.txt', 'build/elastic-stack-dump/check-*/logs/*.log', 'build/elastic-stack-dump/check-*/logs/fleet-server-internal/*'], junitArtifacts: true, publishCoverage: true),
'check-packages-other': generateTestCommandStage(command: 'test-check-packages-other', artifacts: ['build/test-results/*.xml', 'build/elastic-stack-dump/check-*/logs/*.log', 'build/elastic-stack-dump/check-*/logs/fleet-server-internal/*'], junitArtifacts: true, publishCoverage: true),
'check-packages-with-custom-agent': generateTestCommandStage(command: 'test-check-packages-with-custom-agent', artifacts: ['build/test-results/*.xml', 'build/elastic-stack-dump/check-*/logs/*.log', 'build/elastic-stack-dump/check-*/logs/fleet-server-internal/*'], junitArtifacts: true, publishCoverage: true),
'build-zip': generateTestCommandStage(command: 'test-build-zip', artifacts: ['build/elastic-stack-dump/build-zip/logs/*.log', 'build/integrations/*.sig']),
'profiles-command': generateTestCommandStage(command: 'test-profiles-command')
]
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ test-stack-command-8x:

test-stack-command: test-stack-command-default test-stack-command-7x test-stack-command-800 test-stack-command-8x

test-check-packages: test-check-packages-with-kind test-check-packages-other test-check-packages-parallel
test-check-packages: test-check-packages-with-kind test-check-packages-other test-check-packages-parallel test-check-packages-with-custom-agent

test-check-packages-with-kind:
PACKAGE_TEST_TYPE=with-kind ./scripts/test-check-packages.sh
Expand All @@ -76,6 +76,9 @@ test-check-packages-other:
test-check-packages-parallel:
PACKAGE_TEST_TYPE=parallel ./scripts/test-check-packages.sh

test-check-packages-with-custom-agent:
PACKAGE_TEST_TYPE=with-custom-agent ./scripts/test-check-packages.sh

test-build-zip:
./scripts/test-build-zip.sh

Expand Down
53 changes: 53 additions & 0 deletions docs/howto/system_testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ or the data stream's level:

`<service deployer>` - a name of the supported service deployer:
* `docker` - Docker Compose
* `agent` - Custom `elastic-agent` with Docker Compose
* `k8s` - Kubernetes
* `tf` - Terraform

Expand Down Expand Up @@ -106,6 +107,58 @@ volumes:
mysqldata:
```

### Agent service deployer

When using the Agent service deployer, the `elastic-agent` provided by the stack
will not be used. An agent will be deployed as a Docker compose service named `docker-custom-agent`
which base configuration is provided [here](../../internal/install/_static/docker-custom-agent-base.yml).
This configuration will be merged with the one provided in the `custom-agent.yml` file.
This is useful if you need different capabilities than the provided by the
`elastic-agent` used by the `elastic-package stack` command.

`custom-agent.yml`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the custom-agent.yml will be part of a package, we need to cover it with package-spec. You will need to open one more PR.

```
version: '2.3'
services:
docker-custom-agent:
pid: host
cap_add:
- AUDIT_CONTROL
- AUDIT_READ
user: root
```

This will result in an agent configuration such as:

```
version: '2.3'
services:
docker-custom-agent:
hostname: docker-custom-agent
image: "docker.elastic.co/beats/elastic-agent-complete:8.2.0"
pid: host
cap_add:
- AUDIT_CONTROL
- AUDIT_READ
user: root
healthcheck:
test: "elastic-agent status"
retries: 180
interval: 1s
environment:
FLEET_ENROLL: "1"
FLEET_INSECURE: "1"
FLEET_URL: "http://fleet-server:8220"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm, this makes me think that we might need some way to pass variables to these configs. These connection settings depend on the stack and how it is started. For example in #789 I am changing these options, and this would break scenarios with these environment variables.

An option can be to create a different configuration file, expected for example in _dev/deploy/agent/config.yml, that is used to "patch" a base agent configuration. So for example in this case, it could be something like this:

image: "docker.elastic.co/beats/elastic-agent-complete:8.2.0"
pid: host
cap_add:
  - AUDIT_CONTROL
  - AUDIT_READ
user: root

This way every package using this deployer only needs to configure the minimal relevant set of settings. And we can control the settings needed for enrollment, or other settings that may be dependent of elastic-package stack.

Another middle-ground option could be to use env_file here to include an environment file that would be generated by the deployer, and that includes this kind of environment variables needed by agents to connect with the stack.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you're referring to this issue (Extend "profiles" with local patches).

Do you think that we need a proposal to sketch the final look and then iterate on that? Maybe we should focus on this specific issue. It might be tricky if we want "patches" to be backward compatible with older stacks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider this deployer a separate thing to possible general patches for profiles or the stack subcommand. I think that something like this "agent" deployer has value on itself. Even if later we consider more advanced configurations for profiles or the stack subcommand.

The problem I see with general local patches for elastic-package stack is that they may become a source of many reproducibility problems. You may patch the stack to work on one package, and later you start working on a different package and something unexpectedly doesn't work. Or it may be difficult to know or remember that a certain package needs a patched stack.
This could cause a new set of problems similar to the ones with the packages contained in the registry depending on where elastic-package stack up was executed (#599).
And as you mention it may be difficult to support patches with multiple versions of the stack in a general way.

I think it is fine to look for a way to start/patch specialized agents on test time as is being done in this PR. These agents are disposable and developers have more awareness on when they are being started. Packages could also select the version of the agent to use, limiting the problems of supporting multiple versions. Variants could help to test with multiple versions or configurations if there are differences.

And if some day we also have stack-level patches, I think it would be ok if the patches are different to the ones used for this "agent" deployer, at the end they are different things.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine to look for a way to start/patch specialized agents on test time as is being done in this PR.

What about unpatching? Did you plan for this too or is it like the Kubernetes agent, once installed it stays there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine to look for a way to start/patch specialized agents on test time as is being done in this PR.

What about unpatching? Did you plan for this too or is it like the Kubernetes agent, once installed it stays there.

No need to unpatch. Current implementation starts this patched agent as a docker compose service, and destroys it on tear down. So it doesn't stay. I think this is a good approach.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(If I understand it correctly, please @marc-gr correct me if I am wrong 🙂 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is correct 👍

```

And in the test config:

```
data_stream:
vars:
# ...
```


### Terraform service deployer

Expand Down
19 changes: 16 additions & 3 deletions internal/configuration/locations/locations.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ const (
fieldsCachedDir = "cache/fields"

terraformDeployerYmlFile = "terraform-deployer.yml"

dockerCustomAgentDeployerYmlFile = "docker-custom-agent-base.yml"
)

var (
serviceLogsDir = filepath.Join(temporaryDir, "service_logs")
kubernetesDeployerDir = filepath.Join(deployerDir, "kubernetes")
terraformDeployerDir = filepath.Join(deployerDir, "terraform")
serviceLogsDir = filepath.Join(temporaryDir, "service_logs")
kubernetesDeployerDir = filepath.Join(deployerDir, "kubernetes")
terraformDeployerDir = filepath.Join(deployerDir, "terraform")
dockerCustomAgentDeployerDir = filepath.Join(deployerDir, "docker_custom_agent")
)

//LocationManager maintains an instance of a config path location
Expand Down Expand Up @@ -96,6 +99,16 @@ func (loc LocationManager) TerraformDeployerYml() string {
return filepath.Join(loc.stackPath, terraformDeployerDir, terraformDeployerYmlFile)
}

// DockerCustomAgentDeployerDir returns the DockerCustomAgent Directory
func (loc LocationManager) DockerCustomAgentDeployerDir() string {
return filepath.Join(loc.stackPath, dockerCustomAgentDeployerDir)
}

// DockerCustomAgentDeployerYml returns the DockerCustomAgent deployer yml file
func (loc LocationManager) DockerCustomAgentDeployerYml() string {
return filepath.Join(loc.stackPath, dockerCustomAgentDeployerDir, dockerCustomAgentDeployerYmlFile)
}

// ServiceLogDir returns the log directory
func (loc LocationManager) ServiceLogDir() string {
return filepath.Join(loc.stackPath, serviceLogsDir)
Expand Down
15 changes: 15 additions & 0 deletions internal/install/_static/docker-custom-agent-base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "2.3"
services:
docker-custom-agent:
image: "${ELASTIC_AGENT_IMAGE_REF}"
healthcheck:
test: "elastic-agent status"
retries: 180
interval: 1s
hostname: docker-custom-agent
environment:
- FLEET_ENROLL=1
- FLEET_INSECURE=1
- FLEET_URL=http://fleet-server:8220
volumes:
- ${SERVICE_LOGS_DIR}:/tmp/service_logs/
16 changes: 16 additions & 0 deletions internal/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func EnsureInstalled() error {
return errors.Wrap(err, "writing Terraform deployer resources failed")
}

err = writeDockerCustomAgentResources(elasticPackagePath)
if err != nil {
return errors.Wrap(err, "writing Terraform deployer resources failed")
}

if err := createServiceLogsDir(elasticPackagePath); err != nil {
return errors.Wrap(err, "creating service logs directory failed")
}
Expand Down Expand Up @@ -218,6 +223,17 @@ func writeTerraformDeployerResources(elasticPackagePath *locations.LocationManag
return nil
}

func writeDockerCustomAgentResources(elasticPackagePath *locations.LocationManager) error {
dir := elasticPackagePath.DockerCustomAgentDeployerDir()
if err := os.MkdirAll(dir, 0755); err != nil {
return errors.Wrapf(err, "creating directory failed (path: %s)", dir)
}
if err := writeStaticResource(nil, elasticPackagePath.DockerCustomAgentDeployerYml(), dockerCustomAgentBaseYml); err != nil {
return errors.Wrap(err, "writing static resource failed")
}
return nil
}

func writeConfigFile(elasticPackagePath *locations.LocationManager) error {
var err error
err = writeStaticResource(err, filepath.Join(elasticPackagePath.RootDir(), applicationConfigurationYmlFile), applicationConfigurationYml)
Expand Down
3 changes: 3 additions & 0 deletions internal/install/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ var geoIpCountryMmdb string

//go:embed _static/service_tokens
var serviceTokens string

//go:embed _static/docker-custom-agent-base.yml
var dockerCustomAgentBaseYml string
151 changes: 151 additions & 0 deletions internal/testrunner/runners/system/servicedeployer/custom_agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// 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 servicedeployer

import (
_ "embed"
"fmt"

"github.com/pkg/errors"

"github.com/elastic/elastic-package/internal/compose"
"github.com/elastic/elastic-package/internal/configuration/locations"
"github.com/elastic/elastic-package/internal/docker"
"github.com/elastic/elastic-package/internal/files"
"github.com/elastic/elastic-package/internal/install"
"github.com/elastic/elastic-package/internal/kibana"
"github.com/elastic/elastic-package/internal/logger"
"github.com/elastic/elastic-package/internal/stack"
)

const dockerCustomAgentName = "docker-custom-agent"

// CustomAgentDeployer knows how to deploy a custom elastic-agent defined via
// a Docker Compose file.
type CustomAgentDeployer struct {
cfg string
}

// NewCustomAgentDeployer returns a new instance of a deployedCustomAgent.
func NewCustomAgentDeployer(cfgPath string) (*CustomAgentDeployer, error) {
return &CustomAgentDeployer{
cfg: cfgPath,
}, nil
}

// SetUp sets up the service and returns any relevant information.
func (d *CustomAgentDeployer) SetUp(inCtxt ServiceContext) (DeployedService, error) {
logger.Debug("setting up service using Docker Compose service deployer")

appConfig, err := install.Configuration()
if err != nil {
return nil, errors.Wrap(err, "can't read application configuration")
}

kibanaClient, err := kibana.NewClient()
if err != nil {
return nil, errors.Wrap(err, "can't create Kibana client")
}

stackVersion, err := kibanaClient.Version()
if err != nil {
return nil, errors.Wrap(err, "can't read Kibana injected metadata")
}

env := append(
appConfig.StackImageRefs(stackVersion).AsEnv(),
fmt.Sprintf("%s=%s", serviceLogsDirEnv, inCtxt.Logs.Folder.Local),
)

ymlPaths, err := d.loadComposeDefinitions()
if err != nil {
return nil, err
}

service := dockerComposeDeployedService{
ymlPaths: ymlPaths,
project: "elastic-package-service",
sv: ServiceVariant{
Name: dockerCustomAgentName,
Env: env,
},
}

outCtxt := inCtxt

p, err := compose.NewProject(service.project, service.ymlPaths...)
if err != nil {
return nil, errors.Wrap(err, "could not create Docker Compose project for service")
}

// Verify the Elastic stack network
err = stack.EnsureStackNetworkUp()
if err != nil {
return nil, errors.Wrap(err, "Elastic stack network is not ready")
}

// Clean service logs
err = files.RemoveContent(outCtxt.Logs.Folder.Local)
if err != nil {
return nil, errors.Wrap(err, "removing service logs failed")
}

inCtxt.Name = dockerCustomAgentName
serviceName := inCtxt.Name
opts := compose.CommandOptions{
Env: env,
ExtraArgs: []string{"--build", "-d"},
}
err = p.Up(opts)
if err != nil {
return nil, errors.Wrap(err, "could not boot up service using Docker Compose")
}

// Connect service network with stack network (for the purpose of metrics collection)
err = docker.ConnectToNetwork(p.ContainerName(serviceName), stack.Network())
if err != nil {
return nil, errors.Wrapf(err, "can't attach service container to the stack network")
}

err = p.WaitForHealthy(opts)
if err != nil {
processServiceContainerLogs(p, compose.CommandOptions{
Env: opts.Env,
}, outCtxt.Name)
return nil, errors.Wrap(err, "service is unhealthy")
}

// Build service container name
outCtxt.Hostname = p.ContainerName(serviceName)

logger.Debugf("adding service container %s internal ports to context", p.ContainerName(serviceName))
serviceComposeConfig, err := p.Config(compose.CommandOptions{Env: env})
if err != nil {
return nil, errors.Wrap(err, "could not get Docker Compose configuration for service")
}

s := serviceComposeConfig.Services[serviceName]
outCtxt.Ports = make([]int, len(s.Ports))
for idx, port := range s.Ports {
outCtxt.Ports[idx] = port.InternalPort
}

// Shortcut to first port for convenience
if len(outCtxt.Ports) > 0 {
outCtxt.Port = outCtxt.Ports[0]
}
Comment on lines +136 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't remember if this condition is still required.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we keep it then the port can be referenced from the test config and that can be useful in some cases, not a hard requirement though so we can remove it if there is any concern


outCtxt.Agent.Host.NamePrefix = inCtxt.Name
service.ctxt = outCtxt
return &service, nil
}

func (d *CustomAgentDeployer) loadComposeDefinitions() ([]string, error) {
locationManager, err := locations.NewLocationManager()
if err != nil {
return nil, errors.Wrap(err, "can't locate Docker Compose file for Custom Agent deployer")
}
return []string{locationManager.DockerCustomAgentDeployerYml(), d.cfg}, nil
}
7 changes: 7 additions & 0 deletions internal/testrunner/runners/system/servicedeployer/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ func Factory(options FactoryOptions) (ServiceDeployer, error) {
}
return NewDockerComposeServiceDeployer([]string{dockerComposeYMLPath}, sv)
}
case "agent":
customAgentCfgYMLPath := filepath.Join(serviceDeployerPath, "custom-agent.yml")
if _, err := os.Stat(customAgentCfgYMLPath); err != nil {
return nil, errors.Wrap(err, "can't find expected file custom-agent.yml")
}
return NewCustomAgentDeployer(customAgentCfgYMLPath)

case "tf":
if _, err := os.Stat(serviceDeployerPath); err == nil {
return NewTerraformServiceDeployer(serviceDeployerPath)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies:
ecs:
reference: [email protected]
6 changes: 6 additions & 0 deletions test/packages/with-custom-agent/auditd_manager/changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# newer versions go on top
- version: "999.999.999"
changes:
- description: Initial draft of the package
type: enhancement
link: https://github.com/elastic/integrations/pull/1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: "2.3"
services:
docker-custom-agent:
pid: host
cap_add:
- AUDIT_CONTROL
- AUDIT_READ
user: root
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
data_stream:
vars:
audit_rules:
- "-a always,exit -F arch=b64 -S execve,execveat -k exec"
preserve_original_event: true
Loading