Skip to content

Commit

Permalink
[Elastic Agent] Support the install, control, and uninstall of Endpoi…
Browse files Browse the repository at this point in the history
…nt (elastic#19248)

* Initial spec parsing for endpoint.

* Update comment.

* Fix spec test.

* Update code so it copies the entire input.

* Fix ast test.

* Merge agent-improve-restart-loop

* Merge agent-endpoint-spec

* Refactor core/plugin/app into mostly core/ and use core/plugin for different app types.

* Work on endpoint service application.

* More fixes.

* Fix format and tests.

* Fix some imports.

* More cleanups.

* Fix export comment.

* Pass the program.Spec into the descriptor.

* Run endpoint verify, install, and uninstall when endpoint should be running.

* Fix install and uninstall of Endpoint

* Fix some small issues with service app.

* Add changelog entry.

* Fix lint and tests.

* Fix lint.

* Remove the code no longer needed because of newer config format.

* Fix rules and review.

* Update to Endpoint Security.

* Fix issues so endpoint security runs.

* Add comments.

* Update docstring.

* Some more fixes.

* Delete the extra endpoint testdata files.

* Add timeout to exec_file step.

* Fix supported map.

* Fix getting support programs by cmd.

* Improve app started checks.

* Fix buildspec.
  • Loading branch information
blakerouse committed Jun 29, 2020
1 parent ce3f505 commit 0fe1554
Show file tree
Hide file tree
Showing 38 changed files with 435 additions and 106 deletions.
1 change: 1 addition & 0 deletions x-pack/elastic-agent/CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@
- Change stream.* to dataset.* fields {pull}18967[18967]
- Agent now runs the GRPC server and spawned application connect by to Agent {pull}18973[18973]
- Rename input.type logs to logfile {pull}19360[19360]
- Agent now installs/uninstalls Elastic Endpoint {pull}19248[19248]
6 changes: 3 additions & 3 deletions x-pack/elastic-agent/dev-tools/cmd/buildspec/buildspec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions x-pack/elastic-agent/pkg/agent/application/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ func localConfigDefault() *localConfig {
type FleetAgentConfig struct {
API *APIAccess `config:"api" yaml:"api"`
Reporting *LogReporting `config:"reporting" yaml:"reporting"`
Info *AgentInfo `config:"agent_info" yaml:"agent_info"`
Info *AgentInfo `config:"agent" yaml:"agent"`
}

// AgentInfo is a set of agent information.
type AgentInfo struct {
ID string `json:"ID" yaml:"ID" config:"ID"`
ID string `json:"id" yaml:"id" config:"id"`
}

// APIAccess contains the required details to connect to the Kibana endpoint.
Expand Down
8 changes: 4 additions & 4 deletions x-pack/elastic-agent/pkg/agent/application/fleet_decorator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ func injectFleet(cfg *config.Config) func(*logger.Logger, *transpiler.AST) error
}
api, ok := transpiler.Lookup(ast, "api")
if !ok {
return fmt.Errorf("failed to get api from fleet config")
return fmt.Errorf("failed to get api key from fleet config")
}
agentInfo, ok := transpiler.Lookup(ast, "agent_info")
agent, ok := transpiler.Lookup(ast, "agent")
if !ok {
return fmt.Errorf("failed to get agent_info from fleet config")
return fmt.Errorf("failed to get agent key from fleet config")
}
fleet := transpiler.NewDict([]transpiler.Node{agentInfo, api})
fleet := transpiler.NewDict([]transpiler.Node{agent, api})
err = transpiler.Insert(rootAst, fleet, "fleet")
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions x-pack/elastic-agent/pkg/agent/application/info/agent_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import (

// defaultAgentConfigFile is a name of file used to store agent information
const defaultAgentConfigFile = "fleet.yml"
const agentInfoKey = "agent_info"
const agentInfoKey = "agent"

// defaultAgentActionStoreFile is the file that will contains the action that can be replayed after restart.
const defaultAgentActionStoreFile = "action_store.yml"

type persistentAgentInfo struct {
ID string `json:"ID" yaml:"ID" config:"ID"`
ID string `json:"id" yaml:"id" config:"id"`
}

type ioStore interface {
Expand Down
7 changes: 7 additions & 0 deletions x-pack/elastic-agent/pkg/agent/application/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/stateresolver"
downloader "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/download/localremote"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/install"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/uninstall"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/monitoring"
Expand Down Expand Up @@ -66,6 +67,11 @@ func newOperator(ctx context.Context, log *logger.Logger, id routingKey, config
return nil, errors.New(err, "initiating installer")
}

uninstaller, err := uninstall.NewUninstaller()
if err != nil {
return nil, errors.New(err, "initiating uninstaller")
}

stateResolver, err := stateresolver.NewStateResolver(log)
if err != nil {
return nil, err
Expand All @@ -79,6 +85,7 @@ func newOperator(ctx context.Context, log *logger.Logger, id routingKey, config
fetcher,
verifier,
installer,
uninstaller,
stateResolver,
srv,
r,
Expand Down
27 changes: 20 additions & 7 deletions x-pack/elastic-agent/pkg/agent/operation/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import (
"testing"
"time"

"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/program"

operatorCfg "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/operation/config"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/program"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/stateresolver"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/download"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/install"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/uninstall"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/app"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger"
Expand Down Expand Up @@ -55,7 +55,8 @@ func getTestOperator(t *testing.T, downloadPath string, installPath string, p *a

fetcher := &DummyDownloader{}
verifier := &DummyVerifier{}
installer := &DummyInstaller{}
installer := &DummyInstallerChecker{}
uninstaller := &DummyUninstaller{}

stateResolver, err := stateresolver.NewStateResolver(l)
if err != nil {
Expand All @@ -70,7 +71,7 @@ func getTestOperator(t *testing.T, downloadPath string, installPath string, p *a
t.Fatal(err)
}

operator, err := NewOperator(context.Background(), l, "p1", cfg, fetcher, verifier, installer, stateResolver, srv, nil, noop.NewMonitor())
operator, err := NewOperator(context.Background(), l, "p1", cfg, fetcher, verifier, installer, uninstaller, stateResolver, srv, nil, noop.NewMonitor())
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -157,10 +158,22 @@ func (*DummyVerifier) Verify(p, v string) (bool, error) {

var _ download.Verifier = &DummyVerifier{}

type DummyInstaller struct{}
type DummyInstallerChecker struct{}

func (*DummyInstallerChecker) Check(_ context.Context, p, v, _ string) error {
return nil
}

func (*DummyInstallerChecker) Install(_ context.Context, p, v, _ string) error {
return nil
}

var _ install.InstallerChecker = &DummyInstallerChecker{}

type DummyUninstaller struct{}

func (*DummyInstaller) Install(p, v, _ string) error {
func (*DummyUninstaller) Uninstall(_ context.Context, p, v, _ string) error {
return nil
}

var _ install.Installer = &DummyInstaller{}
var _ uninstall.Uninstaller = &DummyUninstaller{}
8 changes: 5 additions & 3 deletions x-pack/elastic-agent/pkg/agent/operation/monitoring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ func getMonitorableTestOperator(t *testing.T, installPath string, m monitoring.M

fetcher := &DummyDownloader{}
verifier := &DummyVerifier{}
installer := &DummyInstaller{}
installer := &DummyInstallerChecker{}
uninstaller := &DummyUninstaller{}

stateResolver, err := stateresolver.NewStateResolver(l)
if err != nil {
Expand All @@ -132,7 +133,7 @@ func getMonitorableTestOperator(t *testing.T, installPath string, m monitoring.M
}

ctx := context.Background()
operator, err := NewOperator(ctx, l, "p1", cfg, fetcher, verifier, installer, stateResolver, srv, nil, m)
operator, err := NewOperator(ctx, l, "p1", cfg, fetcher, verifier, installer, uninstaller, stateResolver, srv, nil, m)
if err != nil {
t.Fatal(err)
}
Expand All @@ -146,7 +147,8 @@ type testMonitorableApp struct {
monitor monitoring.Monitor
}

func (*testMonitorableApp) Name() string { return "" }
func (*testMonitorableApp) Name() string { return "" }
func (*testMonitorableApp) Started() bool { return false }
func (*testMonitorableApp) Start(_ context.Context, _ app.Taggable, cfg map[string]interface{}) error {
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion x-pack/elastic-agent/pkg/agent/operation/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ type operation interface {
// examples:
// - Start does not need to run if process is running
// - Fetch does not need to run if package is already present
Check(application Application) (bool, error)
Check(ctx context.Context, application Application) (bool, error)
// Run runs the operation
Run(ctx context.Context, application Application) error
}

// Application is an application capable of being started, stopped and configured.
type Application interface {
Name() string
Started() bool
Start(ctx context.Context, p app.Taggable, cfg map[string]interface{}) error
Stop()
Configure(ctx context.Context, config map[string]interface{}) error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (o *operationConfig) Name() string {
// Check checks whether config needs to be run.
//
// Always returns true.
func (o *operationConfig) Check(_ Application) (bool, error) { return true, nil }
func (o *operationConfig) Check(_ context.Context, _ Application) (bool, error) { return true, nil }

// Run runs the operation
func (o *operationConfig) Run(ctx context.Context, application Application) (err error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (o *operationFetch) Name() string {
// Check checks whether fetch needs to occur.
//
// If the artifacts already exists then fetch will not be ran.
func (o *operationFetch) Check(_ Application) (bool, error) {
func (o *operationFetch) Check(_ context.Context, _ Application) (bool, error) {
downloadConfig := o.operatorConfig.DownloadConfig
fullPath, err := artifact.GetArtifactPath(o.program.BinaryName(), o.program.Version(), downloadConfig.OS(), downloadConfig.Arch(), downloadConfig.TargetDirectory)
if err != nil {
Expand Down
18 changes: 10 additions & 8 deletions x-pack/elastic-agent/pkg/agent/operation/operation_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package operation

import (
"context"
"os"

"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/operation/config"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/install"
Expand All @@ -20,14 +19,14 @@ type operationInstall struct {
logger *logger.Logger
program Descriptor
operatorConfig *config.Config
installer install.Installer
installer install.InstallerChecker
}

func newOperationInstall(
logger *logger.Logger,
program Descriptor,
operatorConfig *config.Config,
installer install.Installer) *operationInstall {
installer install.InstallerChecker) *operationInstall {

return &operationInstall{
logger: logger,
Expand All @@ -45,10 +44,13 @@ func (o *operationInstall) Name() string {
// Check checks whether install needs to be ran.
//
// If the installation directory already exists then it will not be ran.
func (o *operationInstall) Check(_ Application) (bool, error) {
installDir := o.program.Directory()
_, err := os.Stat(installDir)
return os.IsNotExist(err), nil
func (o *operationInstall) Check(ctx context.Context, _ Application) (bool, error) {
err := o.installer.Check(ctx, o.program.BinaryName(), o.program.Version(), o.program.Directory())
if err != nil {
// don't return err, just state if Run should be called
return true, nil
}
return false, nil
}

// Run runs the operation
Expand All @@ -59,5 +61,5 @@ func (o *operationInstall) Run(ctx context.Context, application Application) (er
}
}()

return o.installer.Install(o.program.BinaryName(), o.program.Version(), o.program.Directory())
return o.installer.Install(ctx, o.program.BinaryName(), o.program.Version(), o.program.Directory())
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (o *operationRemove) Name() string {
// Check checks whether remove needs to run.
//
// Always returns false.
func (o *operationRemove) Check(_ Application) (bool, error) {
func (o *operationRemove) Check(_ context.Context, _ Application) (bool, error) {
return false, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ func (o *retryableOperations) Name() string {
// examples:
// - Start does not need to run if process is running
// - Fetch does not need to run if package is already present
func (o *retryableOperations) Check(application Application) (bool, error) {
func (o *retryableOperations) Check(ctx context.Context, application Application) (bool, error) {
for _, op := range o.operations {
// finish early if at least one operation needs to be run or errored out
if run, err := op.Check(application); err != nil || run {
if run, err := op.Check(ctx, application); err != nil || run {
return run, err
}
}
Expand All @@ -71,7 +71,7 @@ func (o *retryableOperations) runOnce(application Application) func(context.Cont
return ctx.Err()
}

shouldRun, err := op.Check(application)
shouldRun, err := op.Check(ctx, application)
if err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions x-pack/elastic-agent/pkg/agent/operation/operation_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ func (o *operationStart) Name() string {
// Only starts the application when in stopped state, any other state
// and the application is handled by the life cycle inside of the `Application`
// implementation.
func (o *operationStart) Check(application Application) (bool, error) {
if application.State().Status == state.Stopped {
return true, nil
func (o *operationStart) Check(_ context.Context, application Application) (bool, error) {
if application.Started() {
return false, nil
}
return false, nil
return true, nil
}

// Run runs the operation
Expand Down
2 changes: 1 addition & 1 deletion x-pack/elastic-agent/pkg/agent/operation/operation_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (o *operationStop) Name() string {
// Check checks whether application needs to be stopped.
//
// If the application state is not stopped then stop should be performed.
func (o *operationStop) Check(application Application) (bool, error) {
func (o *operationStop) Check(_ context.Context, application Application) (bool, error) {
if application.State().Status != state.Stopped {
return true, nil
}
Expand Down
55 changes: 55 additions & 0 deletions x-pack/elastic-agent/pkg/agent/operation/operation_uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 operation

import (
"context"

"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact/uninstall"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/state"
)

// operationUninstall uninstalls a artifact from predefined location
type operationUninstall struct {
logger *logger.Logger
program Descriptor
uninstaller uninstall.Uninstaller
}

func newOperationUninstall(
logger *logger.Logger,
program Descriptor,
uninstaller uninstall.Uninstaller) *operationUninstall {

return &operationUninstall{
logger: logger,
program: program,
uninstaller: uninstaller,
}
}

// Name is human readable name identifying an operation
func (o *operationUninstall) Name() string {
return "operation-uninstall"
}

// Check checks whether uninstall needs to be ran.
//
// Always true.
func (o *operationUninstall) Check(_ context.Context, _ Application) (bool, error) {
return true, nil
}

// Run runs the operation
func (o *operationUninstall) Run(ctx context.Context, application Application) (err error) {
defer func() {
if err != nil {
application.SetState(state.Failed, err.Error())
}
}()

return o.uninstaller.Uninstall(ctx, o.program.BinaryName(), o.program.Version(), o.program.Directory())
}
Loading

0 comments on commit 0fe1554

Please sign in to comment.