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

[Elastic-Agent] Enable introspecting configuration #18124

Merged
merged 12 commits into from
May 4, 2020
1 change: 1 addition & 0 deletions x-pack/elastic-agent/CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@
- Enable Filebeat input: S3, Azureeventhub, cloudfoundry, httpjson, netflow, o365audit. {pull}17909[17909]
- Use data subfolder as default for process logs {pull}17960[17960]
- Enable debug log level for Metricbeat and Filebeat when run under the Elastic Agent. {pull}17935[17935]
- Enable introspecting configuration {pull}18124[18124]
6 changes: 5 additions & 1 deletion x-pack/elastic-agent/pkg/agent/application/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ type configModifiers struct {
Decorators []decoratorFunc
}

func emitter(log *logger.Logger, router *router, modifiers *configModifiers, reloadables ...reloadable) emitterFunc {
type programsDispatcher interface {
Dispatch(id string, grpProg map[routingKey][]program.Program) error
}

func emitter(log *logger.Logger, router programsDispatcher, modifiers *configModifiers, reloadables ...reloadable) emitterFunc {
return func(c *config.Config) error {
if err := InjectAgentConfig(c); err != nil {
return err
Expand Down
130 changes: 130 additions & 0 deletions x-pack/elastic-agent/pkg/agent/application/introspect_config_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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 application

import (
"fmt"

yaml "gopkg.in/yaml.v2"

"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/storage"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/fleetapi"
)

// IntrospectConfigCmd is an introspect subcommand that shows configurations of the agent.
type IntrospectConfigCmd struct {
cfgPath string
}

// NewIntrospectConfigCmd creates a new introspect command.
func NewIntrospectConfigCmd(configPath string,
) (*IntrospectConfigCmd, error) {
return &IntrospectConfigCmd{
cfgPath: configPath,
}, nil
}

// Execute introspects agent configuration.
func (c *IntrospectConfigCmd) Execute() error {
return c.introspectConfig()
}

func (c *IntrospectConfigCmd) introspectConfig() error {
cfg, err := loadConfig(c.cfgPath)
if err != nil {
return err
}

isLocal, err := isLocalMode(cfg)
if err != nil {
return err
}

if isLocal {
return printConfig(cfg)
}

fleetConfig, err := loadFleetConfig(cfg)
if err != nil {
return err
} else if fleetConfig == nil {
return errors.New("no fleet config retrieved yet")
}

return printMapStringConfig(fleetConfig)
}

func loadConfig(configPath string) (*config.Config, error) {
rawConfig, err := config.LoadYAML(configPath)
if err != nil {
return nil, err
}

if err := InjectAgentConfig(rawConfig); err != nil {
return nil, err
}

return rawConfig, nil
}

func loadFleetConfig(cfg *config.Config) (map[string]interface{}, error) {
log, err := newErrorLogger()
if err != nil {
return nil, err
}

as, err := newActionStore(log, storage.NewDiskStore(fleetActionStoreFile()))
if err != nil {
return nil, err
}

for _, c := range as.Actions() {
cfgChange, ok := c.(*fleetapi.ActionConfigChange)
if !ok {
continue
}

fmt.Println("Action ID:", cfgChange.ID())
return cfgChange.Config, nil
}
return nil, nil
}

func isLocalMode(rawConfig *config.Config) (bool, error) {
c := localDefaultConfig()
if err := rawConfig.Unpack(&c); err != nil {
return false, errors.New(err, "initiating application")
}

managementConfig := struct {
Mode string `config:"mode" yaml:"mode"`
}{}

if err := c.Management.Unpack(&managementConfig); err != nil {
return false, errors.New(err, "initiating application")
}
return managementConfig.Mode == "local", nil
}

func printMapStringConfig(mapStr map[string]interface{}) error {
data, err := yaml.Marshal(mapStr)
if err != nil {
return errors.New(err, "could not marshal to YAML")
}

fmt.Println(string(data))
return nil
}

func printConfig(cfg *config.Config) error {
mapStr, err := cfg.ToMapStr()
if err != nil {
return err
}

return printMapStringConfig(mapStr)
}
211 changes: 211 additions & 0 deletions x-pack/elastic-agent/pkg/agent/application/introspect_output_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// 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 application

import (
"fmt"

"github.com/urso/ecslog"
"github.com/urso/ecslog/backend"
"github.com/urso/ecslog/backend/appender"
"github.com/urso/ecslog/backend/layout"

"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/filters"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors"
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/program"
"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/plugin/app/monitoring/noop"
)

// IntrospectOutputCmd is an introspect subcommand that shows configurations of the agent.
type IntrospectOutputCmd struct {
cfgPath string
output string
program string
}

// NewIntrospectOutputCmd creates a new introspect command.
func NewIntrospectOutputCmd(configPath, output, program string) (*IntrospectOutputCmd, error) {
return &IntrospectOutputCmd{
cfgPath: configPath,
output: output,
program: program,
}, nil
}

// Execute tries to enroll the agent into Fleet.
func (c *IntrospectOutputCmd) Execute() error {
if c.output == "" {
return c.introspectOutputs()
}

return c.introspectOutput()
}

func (c *IntrospectOutputCmd) introspectOutputs() error {
cfg, err := loadConfig(c.cfgPath)
if err != nil {
return err
}

isLocal, err := isLocalMode(cfg)
if err != nil {
return err
}

l, err := newErrorLogger()
if err != nil {
return err
}

if isLocal {
return listOutputsFromConfig(l, cfg)
}

fleetConfig, err := loadFleetConfig(cfg)
if err != nil {
return err
} else if fleetConfig == nil {
return errors.New("no fleet config retrieved yet")
}

return listOutputsFromMap(l, fleetConfig)
}

func listOutputsFromConfig(log *logger.Logger, cfg *config.Config) error {
programsGroup, err := getProgramsFromConfig(log, cfg)
if err != nil {
return err

}

for k := range programsGroup {
fmt.Println(k)
}

return nil
}

func listOutputsFromMap(log *logger.Logger, cfg map[string]interface{}) error {
c, err := config.NewConfigFrom(cfg)
if err != nil {
return err
}

return listOutputsFromConfig(log, c)
}

func (c *IntrospectOutputCmd) introspectOutput() error {
cfg, err := loadConfig(c.cfgPath)
if err != nil {
return err
}

l, err := newErrorLogger()
if err != nil {
return err
}

isLocal, err := isLocalMode(cfg)
if err != nil {
return err
}

if isLocal {
return printOutputFromConfig(l, c.output, c.program, cfg)
}

fleetConfig, err := loadFleetConfig(cfg)
if err != nil {
return err
} else if fleetConfig == nil {
return errors.New("no fleet config retrieved yet")
}

return printOutputFromMap(l, c.output, c.program, fleetConfig)
}

func printOutputFromConfig(log *logger.Logger, output, programName string, cfg *config.Config) error {
programsGroup, err := getProgramsFromConfig(log, cfg)
if err != nil {
return err

}

for k, programs := range programsGroup {
if k != output {
continue
}

var programFound bool
for _, p := range programs {
if programName != "" && programName != p.Spec.Cmd {
continue
}

programFound = true
fmt.Printf("[%s] %s:\n", k, p.Spec.Cmd)
printMapStringConfig(p.Configuration())
fmt.Println("---")
}

if !programFound {
fmt.Printf("program '%s' is not recognized within output '%s', try running `elastic-agent introspect output` to find available outputs.\n",
programName,
output)
}
return nil
}

fmt.Printf("output '%s' is not recognized, try running `elastic-agent introspect output` to find available outputs.\n", output)

return nil
}

func printOutputFromMap(log *logger.Logger, output, programName string, cfg map[string]interface{}) error {
c, err := config.NewConfigFrom(cfg)
if err != nil {
return err
}

return printOutputFromConfig(log, output, programName, c)
}

func getProgramsFromConfig(log *logger.Logger, cfg *config.Config) (map[string][]program.Program, error) {
monitor := noop.NewMonitor()
router := &inmemRouter{}
emit := emitter(
log,
router,
&configModifiers{
Decorators: []decoratorFunc{injectMonitoring},
Filters: []filterFunc{filters.ConstraintFilter},
},
monitor,
)

if err := emit(cfg); err != nil {
return nil, err
}
return router.programs, nil
}

type inmemRouter struct {
programs map[string][]program.Program
}

func (r *inmemRouter) Dispatch(id string, grpProg map[routingKey][]program.Program) error {
r.programs = grpProg
return nil
}

func newErrorLogger() (*logger.Logger, error) {
backend, err := appender.Console(backend.Error, layout.Text(true))
if err != nil {
return nil, err
}
return ecslog.New(backend), nil
}
1 change: 1 addition & 0 deletions x-pack/elastic-agent/pkg/agent/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command {
cmd.AddCommand(basecmd.NewDefaultCommandsWithArgs(args, streams)...)
cmd.AddCommand(newRunCommandWithArgs(flags, args, streams))
cmd.AddCommand(newEnrollCommandWithArgs(flags, args, streams))
cmd.AddCommand(newIntrospectCommandWithArgs(flags, args, streams))

return cmd
}
Loading