@@ -15,6 +15,8 @@ import (
1515 "os"
1616 "time"
1717
18+ "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/process"
19+
1820 "gopkg.in/yaml.v2"
1921
2022 "github.com/elastic/beats/v7/libbeat/common/backoff"
@@ -72,6 +74,19 @@ type EnrollCmd struct {
7274 client clienter
7375 configStore store
7476 kibanaConfig * kibana.Config
77+ agentProc * process.Info
78+ }
79+
80+ // EnrollCmdFleetServerOption define all the supported enrollment options for bootstrapping with Fleet Server.
81+ type EnrollCmdFleetServerOption struct {
82+ ConnStr string
83+ PolicyID string
84+ Host string
85+ Port uint16
86+ Cert string
87+ CertKey string
88+ Insecure bool
89+ SpawnAgent bool
7590}
7691
7792// EnrollCmdOption define all the supported enrollment option.
@@ -84,13 +99,7 @@ type EnrollCmdOption struct {
8499 UserProvidedMetadata map [string ]interface {}
85100 EnrollAPIKey string
86101 Staging string
87- FleetServerConnStr string
88- FleetServerPolicyID string
89- FleetServerHost string
90- FleetServerPort uint16
91- FleetServerCert string
92- FleetServerCertKey string
93- FleetServerInsecure bool
102+ FleetServer EnrollCmdFleetServerOption
94103}
95104
96105func (e * EnrollCmdOption ) kibanaConfig () (* kibana.Config , error ) {
@@ -157,7 +166,8 @@ func NewEnrollCmdWithStore(
157166// Execute tries to enroll the agent into Fleet.
158167func (c * EnrollCmd ) Execute (ctx context.Context ) error {
159168 var err error
160- if c .options .FleetServerConnStr != "" {
169+ defer c .stopAgent () // ensure its stopped no matter what
170+ if c .options .FleetServer .ConnStr != "" {
161171 err = c .fleetServerBootstrap (ctx )
162172 if err != nil {
163173 return err
@@ -185,18 +195,26 @@ func (c *EnrollCmd) Execute(ctx context.Context) error {
185195 return errors .New (err , "fail to enroll" )
186196 }
187197
188- if c .daemonReload (ctx ) != nil {
189- c .log .Info ("Elastic Agent might not be running; unable to trigger restart" )
198+ if c .agentProc == nil {
199+ if c .daemonReload (ctx ) != nil {
200+ c .log .Info ("Elastic Agent might not be running; unable to trigger restart" )
201+ }
202+ c .log .Info ("Successfully triggered restart on running Elastic Agent." )
203+ return nil
190204 }
191- c .log .Info ("Successfully triggered restart on running Elastic Agent. " )
205+ c .log .Info ("Elastic Agent has been enrolled; start Elastic Agent" )
192206 return nil
193207}
194208
195209func (c * EnrollCmd ) fleetServerBootstrap (ctx context.Context ) error {
196210 c .log .Debug ("verifying communication with running Elastic Agent daemon" )
211+ agentRunning := true
197212 _ , err := getDaemonStatus (ctx )
198213 if err != nil {
199- return errors .New ("failed to communicate with elastic-agent daemon; is elastic-agent running?" )
214+ if ! c .options .FleetServer .SpawnAgent {
215+ return errors .New ("failed to communicate with elastic-agent daemon; is elastic-agent running?" )
216+ }
217+ agentRunning = false
200218 }
201219
202220 err = c .prepareFleetTLS ()
@@ -205,9 +223,9 @@ func (c *EnrollCmd) fleetServerBootstrap(ctx context.Context) error {
205223 }
206224
207225 fleetConfig , err := createFleetServerBootstrapConfig (
208- c .options .FleetServerConnStr , c .options .FleetServerPolicyID ,
209- c .options .FleetServerHost , c .options .FleetServerPort ,
210- c .options .FleetServerCert , c .options .FleetServerCertKey )
226+ c .options .FleetServer . ConnStr , c .options .FleetServer . PolicyID ,
227+ c .options .FleetServer . Host , c .options .FleetServer . Port ,
228+ c .options .FleetServer . Cert , c .options .FleetServer . CertKey )
211229 configToStore := map [string ]interface {}{
212230 "fleet" : fleetConfig ,
213231 }
@@ -219,9 +237,18 @@ func (c *EnrollCmd) fleetServerBootstrap(ctx context.Context) error {
219237 return errors .New (err , "could not save fleet server bootstrap information" , errors .TypeFilesystem )
220238 }
221239
222- err = c .daemonReload (ctx )
223- if err != nil {
224- return errors .New (err , "failed to trigger elastic-agent daemon reload" , errors .TypeApplication )
240+ if agentRunning {
241+ // reload the already running agent
242+ err = c .daemonReload (ctx )
243+ if err != nil {
244+ return errors .New (err , "failed to trigger elastic-agent daemon reload" , errors .TypeApplication )
245+ }
246+ } else {
247+ // spawn `run` as a subprocess so enroll can perform the bootstrap process of Fleet Server
248+ err = c .startAgent ()
249+ if err != nil {
250+ return err
251+ }
225252 }
226253
227254 err = waitForFleetServer (ctx , c .log )
@@ -232,25 +259,25 @@ func (c *EnrollCmd) fleetServerBootstrap(ctx context.Context) error {
232259}
233260
234261func (c * EnrollCmd ) prepareFleetTLS () error {
235- host := c .options .FleetServerHost
262+ host := c .options .FleetServer . Host
236263 if host == "" {
237264 host = "localhost"
238265 }
239- port := c .options .FleetServerPort
266+ port := c .options .FleetServer . Port
240267 if port == 0 {
241268 port = defaultFleetServerPort
242269 }
243- if c .options .FleetServerCert != "" && c .options .FleetServerCertKey == "" {
270+ if c .options .FleetServer . Cert != "" && c .options .FleetServer . CertKey == "" {
244271 return errors .New ("certificate private key is required when certificate provided" )
245272 }
246- if c .options .FleetServerCertKey != "" && c .options .FleetServerCert == "" {
273+ if c .options .FleetServer . CertKey != "" && c .options .FleetServer . Cert == "" {
247274 return errors .New ("certificate is required when certificate private key is provided" )
248275 }
249- if c .options .FleetServerCert == "" && c .options .FleetServerCertKey == "" {
250- if c .options .FleetServerInsecure {
276+ if c .options .FleetServer . Cert == "" && c .options .FleetServer . CertKey == "" {
277+ if c .options .FleetServer . Insecure {
251278 // running insecure, force the binding to localhost (unless specified)
252- if c .options .FleetServerHost == "" {
253- c .options .FleetServerHost = "localhost"
279+ if c .options .FleetServer . Host == "" {
280+ c .options .FleetServer . Host = "localhost"
254281 }
255282 c .options .URL = fmt .Sprintf ("http://%s:%d" , host , port )
256283 c .options .Insecure = true
@@ -270,8 +297,8 @@ func (c *EnrollCmd) prepareFleetTLS() error {
270297 if err != nil {
271298 return err
272299 }
273- c .options .FleetServerCert = string (pair .Crt )
274- c .options .FleetServerCertKey = string (pair .Key )
300+ c .options .FleetServer . Cert = string (pair .Crt )
301+ c .options .FleetServer . CertKey = string (pair .Key )
275302 c .options .URL = fmt .Sprintf ("https://%s:%d" , hostname , port )
276303 c .options .CAs = []string {string (ca .Crt ())}
277304 }
@@ -295,8 +322,18 @@ func (c *EnrollCmd) enrollWithBackoff(ctx context.Context) error {
295322 signal := make (chan struct {})
296323 backExp := backoff .NewExpBackoff (signal , 60 * time .Second , 10 * time .Minute )
297324
298- for errors .Is (err , fleetapi .ErrTooManyRequests ) {
299- c .log .Warn ("Too many requests on the remote server, will retry in a moment." )
325+ for {
326+ retry := false
327+ if errors .Is (err , fleetapi .ErrTooManyRequests ) {
328+ c .log .Warn ("Too many requests on the remote server, will retry in a moment." )
329+ retry = true
330+ } else if errors .Is (err , fleetapi .ErrConnRefused ) {
331+ c .log .Warn ("Remote server is not ready to accept connections, will retry in a moment." )
332+ retry = true
333+ }
334+ if ! retry {
335+ break
336+ }
300337 backExp .Wait ()
301338 c .log .Info ("Retrying to enroll..." )
302339 err = c .enroll (ctx )
@@ -344,11 +381,11 @@ func (c *EnrollCmd) enroll(ctx context.Context) error {
344381 "sourceURI" : staging ,
345382 }
346383 }
347- if c .options .FleetServerConnStr != "" {
384+ if c .options .FleetServer . ConnStr != "" {
348385 serverConfig , err := createFleetServerBootstrapConfig (
349- c .options .FleetServerConnStr , c .options .FleetServerPolicyID ,
350- c .options .FleetServerHost , c .options .FleetServerPort ,
351- c .options .FleetServerCert , c .options .FleetServerCertKey )
386+ c .options .FleetServer . ConnStr , c .options .FleetServer . PolicyID ,
387+ c .options .FleetServer . Host , c .options .FleetServer . Port ,
388+ c .options .FleetServer . Cert , c .options .FleetServer . CertKey )
352389 if err != nil {
353390 return err
354391 }
@@ -390,6 +427,27 @@ func (c *EnrollCmd) enroll(ctx context.Context) error {
390427 return nil
391428}
392429
430+ func (c * EnrollCmd ) startAgent () error {
431+ cmd , err := os .Executable ()
432+ if err != nil {
433+ return err
434+ }
435+ c .log .Info ("Spawning Elastic Agent daemon as a subprocess to complete bootstrap process." )
436+ proc , err := process .Start (c .log , cmd , nil , os .Geteuid (), os .Getegid (), "run" )
437+ if err != nil {
438+ return err
439+ }
440+ c .agentProc = proc
441+ return nil
442+ }
443+
444+ func (c * EnrollCmd ) stopAgent () {
445+ if c .agentProc != nil {
446+ c .agentProc .StopWait ()
447+ c .agentProc = nil
448+ }
449+ }
450+
393451func yamlToReader (in interface {}) (io.Reader , error ) {
394452 data , err := yaml .Marshal (in )
395453 if err != nil {
@@ -461,6 +519,10 @@ func waitForFleetServer(ctx context.Context, log *logger.Logger) error {
461519 // app has started and is running
462520 resChan <- waitResult {}
463521 break
522+ } else if app .Status == proto .Status_FAILED {
523+ // app completely failed; exit now
524+ resChan <- waitResult {err : errors .New (app .Message )}
525+ break
464526 }
465527 if app .Message != "" {
466528 appMsg := fmt .Sprintf ("Fleet Server - %s" , app .Message )
0 commit comments