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

[OTEL-2317] Add status section for OTel Agent #32938

Merged
merged 20 commits into from
Feb 3, 2025
Merged
2 changes: 2 additions & 0 deletions cmd/otel-agent/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands"
"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands/run"
"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands/status"
"github.com/DataDog/datadog-agent/pkg/cli/subcommands/version"
"go.opentelemetry.io/collector/featuregate"
)
Expand Down Expand Up @@ -48,6 +49,7 @@ func makeCommands(globalParams *subcommands.GlobalParams) *cobra.Command {
}
commands := []*cobra.Command{
run.MakeCommand(globalConfGetter),
status.MakeCommand(globalConfGetter),
version.MakeCommand("otel-agent"),
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/otel-agent/subcommands/run/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
logfx "github.com/DataDog/datadog-agent/comp/core/log/fx"
logtracefx "github.com/DataDog/datadog-agent/comp/core/log/fx-trace"
"github.com/DataDog/datadog-agent/comp/core/secrets"
"github.com/DataDog/datadog-agent/comp/core/status/statusimpl"
tagger "github.com/DataDog/datadog-agent/comp/core/tagger/def"
remoteTaggerFx "github.com/DataDog/datadog-agent/comp/core/tagger/fx-remote"
taggerTypes "github.com/DataDog/datadog-agent/comp/core/tagger/types"
Expand Down Expand Up @@ -219,6 +220,7 @@ func runOTelAgentCommand(ctx context.Context, params *subcommands.GlobalParams,
PIDFilePath: "",
}),
traceagentfx.Module(),
statusimpl.Module(),
)
}

Expand Down
240 changes: 240 additions & 0 deletions cmd/otel-agent/subcommands/status/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

// Package status implements 'agent status'.
package status

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands"
"net/url"
"os"

"go.uber.org/fx"

"github.com/DataDog/datadog-agent/cmd/agent/command"
"github.com/DataDog/datadog-agent/comp/core"
"github.com/DataDog/datadog-agent/comp/core/config"
log "github.com/DataDog/datadog-agent/comp/core/log/def"
"github.com/DataDog/datadog-agent/comp/core/sysprobeconfig"
apiutil "github.com/DataDog/datadog-agent/pkg/api/util"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
"github.com/DataDog/datadog-agent/pkg/util/scrubber"

"github.com/spf13/cobra"
)

// cliParams are the command-line arguments for this subcommand
type cliParams struct {
*subcommands.GlobalParams

// args are the positional command-line arguments
args []string

jsonStatus bool
prettyPrintJSON bool
statusFilePath string
verbose bool
list bool
logLevelDefaultOff command.LogLevelDefaultOff
}

// MakeCommand returns a slice of subcommands for the 'agent' command.
func MakeCommand(globalConfGetter func() *subcommands.GlobalParams) *cobra.Command {
globalParams := globalConfGetter()
cliParams := &cliParams{
GlobalParams: globalParams,
}
cmd := &cobra.Command{
Use: "status [section]",
Short: "Display the current status",
Long: `Display the current status.
If no section is specified, this command will display all status sections.
If a specific section is provided, such as 'collector', it will only display the status of that section.
The --list flag can be used to list all available status sections.`,
RunE: func(_ *cobra.Command, args []string) error {
cliParams.args = args

// Prevent autoconfig to run when running status as it logs before logger
// is setup. Cannot rely on config.Override as env detection is run before
// overrides are set. TODO: This should eventually be handled with a
// BundleParams field for AD.
os.Setenv("DD_AUTOCONFIG_FROM_ENVIRONMENT", "false")

return fxutil.OneShot(statusCmd,
fx.Supply(cliParams),
fx.Supply(core.BundleParams{
ConfigParams: config.NewAgentParams(globalParams.CoreConfPath, config.WithExtraConfFiles(globalParams.ConfPaths)),
LogParams: log.ForOneShot(command.LoggerName, cliParams.logLevelDefaultOff.Value(), true)}),
core.Bundle(),
)
},
}
cliParams.logLevelDefaultOff.Register(cmd)
cmd.PersistentFlags().BoolVarP(&cliParams.jsonStatus, "json", "j", false, "print out raw json")
cmd.PersistentFlags().BoolVarP(&cliParams.prettyPrintJSON, "pretty-json", "p", false, "pretty print JSON")
cmd.PersistentFlags().StringVarP(&cliParams.statusFilePath, "file", "o", "", "Output the status command to a file")
cmd.PersistentFlags().BoolVarP(&cliParams.verbose, "verbose", "v", false, "print out verbose status")
cmd.PersistentFlags().BoolVarP(&cliParams.list, "list", "l", false, "list all available status sections")

return cmd
}

func scrubMessage(message string) string {
msgScrubbed, err := scrubber.ScrubBytes([]byte(message))
if err == nil {
return string(msgScrubbed)
}
return "[REDACTED] - failure to clean the message"
}

func redactError(unscrubbedError error) error {
liustanley marked this conversation as resolved.
Show resolved Hide resolved
if unscrubbedError == nil {
return unscrubbedError
}

errMsg := unscrubbedError.Error()
scrubbedMsg, scrubOperationErr := scrubber.ScrubBytes([]byte(errMsg))
var scrubbedError error
if scrubOperationErr != nil {
scrubbedError = errors.New("[REDACTED] failed to clean error")
} else {
scrubbedError = errors.New(string(scrubbedMsg))
}

return scrubbedError
}

func statusCmd(logger log.Component, config config.Component, _ sysprobeconfig.Component, cliParams *cliParams) error {
if cliParams.list {
return redactError(requestSections(config))
}

if len(cliParams.args) < 1 {
return redactError(requestStatus(config, cliParams))
}

return componentStatusCmd(logger, config, cliParams)
}

func setIpcURL(cliParams *cliParams) url.Values {
v := url.Values{}
if cliParams.verbose {
v.Set("verbose", "true")
}

if cliParams.prettyPrintJSON || cliParams.jsonStatus {
v.Set("format", "json")
} else {
v.Set("format", "text")
}

return v
}

func renderResponse(res []byte, cliParams *cliParams) error {
var s string

// The rendering is done in the client so that the agent has less work to do
if cliParams.prettyPrintJSON {
var prettyJSON bytes.Buffer
json.Indent(&prettyJSON, res, "", " ") //nolint:errcheck
s = prettyJSON.String()
} else if cliParams.jsonStatus {
s = string(res)
} else {
s = scrubMessage(string(res))
liustanley marked this conversation as resolved.
Show resolved Hide resolved
}

if cliParams.statusFilePath != "" {
return os.WriteFile(cliParams.statusFilePath, []byte(s), 0644)
}
fmt.Println(s)
return nil
}

func requestStatus(config config.Component, cliParams *cliParams) error {

if !cliParams.prettyPrintJSON && !cliParams.jsonStatus {
fmt.Printf("Getting the status from the agent.\n\n")
}

v := setIpcURL(cliParams)

endpoint, err := apiutil.NewIPCEndpoint(config, "/agent/status")
if err != nil {
return err
}

res, err := endpoint.DoGet(apiutil.WithValues(v))
if err != nil {
return err
}

// The rendering is done in the client so that the agent has less work to do
err = renderResponse(res, cliParams)
if err != nil {
return err
}

return nil
}

func componentStatusCmd(_ log.Component, config config.Component, cliParams *cliParams) error {
if len(cliParams.args) > 1 {
return fmt.Errorf("only one section must be specified")
}

return redactError(componentStatus(config, cliParams, cliParams.args[0]))
}

func componentStatus(config config.Component, cliParams *cliParams, component string) error {

v := setIpcURL(cliParams)

endpoint, err := apiutil.NewIPCEndpoint(config, fmt.Sprintf("/agent/%s/status", component))
if err != nil {
return err
}
res, err := endpoint.DoGet(apiutil.WithValues(v))
if err != nil {
return err
}

// The rendering is done in the client so that the agent has less work to do
err = renderResponse(res, cliParams)
if err != nil {
return err
}

return nil
}

func requestSections(config config.Component) error {
endpoint, err := apiutil.NewIPCEndpoint(config, "/agent/status/sections")
if err != nil {
return err
}

res, err := endpoint.DoGet()
if err != nil {
return err
}

var sections []string
err = json.Unmarshal(res, &sections)
if err != nil {
return err
}

for _, section := range sections {
fmt.Printf("- \"%s\"\n", section)
}

return nil
}
53 changes: 53 additions & 0 deletions cmd/otel-agent/subcommands/status/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package status

import (
"github.com/spf13/cobra"
"os"
"testing"

"github.com/stretchr/testify/require"

"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands"
"github.com/DataDog/datadog-agent/comp/core"
"github.com/DataDog/datadog-agent/comp/core/secrets"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
)

func globalConfGetter() *subcommands.GlobalParams {
return &subcommands.GlobalParams{
ConfigName: "datadog-otel",
LoggerName: "logger",
}
}

func TestStatusCommand(t *testing.T) {
defer os.Unsetenv("DD_AUTOCONFIG_FROM_ENVIRONMENT") // undo os.Setenv by RunE

fxutil.TestOneShotSubcommand(t,
[]*cobra.Command{MakeCommand(globalConfGetter)},
[]string{"status", "-j"},
statusCmd,
func(cliParams *cliParams, _ core.BundleParams, secretParams secrets.Params) {
require.Equal(t, []string{}, cliParams.args)
require.Equal(t, true, cliParams.jsonStatus)
require.Equal(t, false, secretParams.Enabled)
})
}

func TestComponentStatusCommand(t *testing.T) {
defer os.Unsetenv("DD_AUTOCONFIG_FROM_ENVIRONMENT") // undo os.Setenv by RunE
fxutil.TestOneShotSubcommand(t,
[]*cobra.Command{MakeCommand(globalConfGetter)},
[]string{"status", "component", "abc"},
statusCmd,
func(cliParams *cliParams, _ core.BundleParams, secretParams secrets.Params) {
require.Equal(t, []string{"component", "abc"}, cliParams.args)
require.Equal(t, false, cliParams.jsonStatus)
require.Equal(t, false, secretParams.Enabled)
})
}
Loading