Skip to content

Commit dec0856

Browse files
committed
Add status subcommand to report status of running daemon. (#24856)
* Add status sub-command to report status of running daemon. * Set exit code based on health status. * Add changelog. * Fix format. (cherry picked from commit 23efd31)
1 parent a316dc0 commit dec0856

3 files changed

Lines changed: 124 additions & 0 deletions

File tree

x-pack/elastic-agent/CHANGELOG.next.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,4 @@
7979
- Add support for Fleet Server running under Elastic Agent {pull}24220[24220]
8080
- Add CA support to Elastic Agent docker image {pull}24486[24486]
8181
- Add STATE_PATH, CONFIG_PATH, LOGS_PATH to Elastic Agent docker image {pull}24817[24817]
82+
- Add status subcommand {pull}24856[24856]

x-pack/elastic-agent/pkg/agent/cmd/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command {
5353
cmd.AddCommand(newInspectCommandWithArgs(args, streams))
5454
cmd.AddCommand(newWatchCommandWithArgs(args, streams))
5555
cmd.AddCommand(newContainerCommand(args, streams))
56+
cmd.AddCommand(newStatusCommand(args, streams))
5657

5758
// windows special hidden sub-command (only added on windows)
5859
reexec := newReExecWindowsCommand(args, streams)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package cmd
6+
7+
import (
8+
"context"
9+
"encoding/json"
10+
"fmt"
11+
"io"
12+
"os"
13+
"time"
14+
15+
"gopkg.in/yaml.v2"
16+
17+
"github.com/spf13/cobra"
18+
19+
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/control/client"
20+
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors"
21+
"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli"
22+
)
23+
24+
type outputter func(io.Writer, *client.AgentStatus) error
25+
26+
var outputs = map[string]outputter{
27+
"human": humanOutput,
28+
"json": jsonOutput,
29+
"yaml": yamlOutput,
30+
}
31+
32+
func newStatusCommand(_ []string, streams *cli.IOStreams) *cobra.Command {
33+
cmd := &cobra.Command{
34+
Use: "status",
35+
Short: "Status returns the current status of the running Elastic Agent daemon.",
36+
Long: `Status returns the current status of the running Elastic Agent daemon.`,
37+
Run: func(c *cobra.Command, args []string) {
38+
if err := statusCmd(streams, c, args); err != nil {
39+
fmt.Fprintf(streams.Err, "Error: %v\n", err)
40+
os.Exit(1)
41+
}
42+
},
43+
}
44+
45+
cmd.Flags().String("output", "human", "Output the status information in either human, json, or yaml (default: human)")
46+
47+
return cmd
48+
}
49+
50+
func statusCmd(streams *cli.IOStreams, cmd *cobra.Command, args []string) error {
51+
output, _ := cmd.Flags().GetString("output")
52+
outputFunc, ok := outputs[output]
53+
if !ok {
54+
return fmt.Errorf("unsupported output: %s", output)
55+
}
56+
57+
ctx := handleSignal(context.Background())
58+
innerCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
59+
defer cancel()
60+
61+
status, err := getDaemonStatus(innerCtx)
62+
if err == context.DeadlineExceeded {
63+
return errors.New("timed out after 30 seconds trying to connect to Elastic Agent daemon")
64+
} else if err == context.Canceled {
65+
return nil
66+
} else if err != nil {
67+
return fmt.Errorf("failed to communicate with Elastic Agent daemon: %s", err)
68+
}
69+
70+
err = outputFunc(streams.Out, status)
71+
if err != nil {
72+
return err
73+
}
74+
// exit 0 only if the Elastic Agent daemon is healthy
75+
if status.Status == client.Healthy {
76+
os.Exit(0)
77+
} else {
78+
os.Exit(1)
79+
}
80+
return nil
81+
}
82+
83+
func humanOutput(w io.Writer, status *client.AgentStatus) error {
84+
fmt.Fprintf(w, "Status: %s\n", status.Status)
85+
if status.Message == "" {
86+
fmt.Fprint(w, "Message: (no message)\n")
87+
} else {
88+
fmt.Fprintf(w, "Message: %s\n", status.Message)
89+
}
90+
if len(status.Applications) == 0 {
91+
fmt.Fprint(w, "Applications: (none)\n")
92+
} else {
93+
fmt.Fprint(w, "Applications:\n")
94+
for _, app := range status.Applications {
95+
fmt.Fprintf(w, " * %s\t(%s)\n", app.Name, app.Status)
96+
if app.Message == "" {
97+
fmt.Fprint(w, " (no message)\n")
98+
} else {
99+
fmt.Fprintf(w, " %s\n", app.Message)
100+
}
101+
}
102+
}
103+
return nil
104+
}
105+
106+
func jsonOutput(w io.Writer, status *client.AgentStatus) error {
107+
bytes, err := json.MarshalIndent(status, "", " ")
108+
if err != nil {
109+
return err
110+
}
111+
fmt.Fprintf(w, "%s\n", bytes)
112+
return nil
113+
}
114+
115+
func yamlOutput(w io.Writer, status *client.AgentStatus) error {
116+
bytes, err := yaml.Marshal(status)
117+
if err != nil {
118+
return err
119+
}
120+
fmt.Fprintf(w, "%s\n", bytes)
121+
return nil
122+
}

0 commit comments

Comments
 (0)