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+
15package serverless
26
37import (
4- "bytes"
58 "context"
69 "encoding/json"
710 "fmt"
8- "io"
911 "net/http"
12+ "time"
13+
14+ "github.com/elastic/elastic-package/internal/logger"
1015)
1116
1217// Project represents a serverless project
@@ -16,6 +21,7 @@ type Project struct {
1621
1722 Name string `json:"name"`
1823 ID string `json:"id"`
24+ Alias string `json:"alias"`
1925 Type string `json:"type"`
2026 Region string `json:"region_id"`
2127
@@ -32,44 +38,126 @@ type Project struct {
3238 } `json:"endpoints"`
3339}
3440
35- // NewObservabilityProject creates a new observability type project
36- func NewObservabilityProject (ctx context.Context , url , name , apiKey , region string ) (* Project , error ) {
37- return newProject (ctx , url , name , apiKey , region , "observability" )
41+ type serviceHealthy func (context.Context , * Project ) error
42+
43+ func (p * Project ) EnsureHealthy (ctx context.Context ) error {
44+ if err := p .ensureServiceHealthy (ctx , getESHealthy ); err != nil {
45+ return fmt .Errorf ("elasticsearch not healthy: %w" , err )
46+ }
47+ if err := p .ensureServiceHealthy (ctx , getKibanaHealthy ); err != nil {
48+ return fmt .Errorf ("kibana not healthy: %w" , err )
49+ }
50+ if err := p .ensureServiceHealthy (ctx , getFleetHealthy ); err != nil {
51+ return fmt .Errorf ("fleet not healthy: %w" , err )
52+ }
53+ return nil
3854}
3955
40- // newProject creates a new serverless project
41- // Note that the Project.Endpoints may not be populated and another call may be required.
42- func newProject (ctx context.Context , url , name , apiKey , region , projectType string ) (* Project , error ) {
43- ReqBody := struct {
44- Name string `json:"name"`
45- RegionID string `json:"region_id"`
46- }{
47- Name : name ,
48- RegionID : region ,
49- }
50- p , err := json .Marshal (ReqBody )
56+ func (p * Project ) ensureServiceHealthy (ctx context.Context , serviceFunc serviceHealthy ) error {
57+ timer := time .NewTimer (time .Millisecond )
58+ for {
59+ select {
60+ case <- ctx .Done ():
61+ return ctx .Err ()
62+ case <- timer .C :
63+ }
64+
65+ err := serviceFunc (ctx , p )
66+ if err != nil {
67+ logger .Debugf ("service not ready: %s" , err .Error ())
68+ timer .Reset (time .Second * 5 )
69+ continue
70+ }
71+
72+ return nil
73+ }
74+ return nil
75+ }
76+
77+ func getESHealthy (ctx context.Context , project * Project ) error {
78+ client , err := NewClient (
79+ WithAddress (project .Endpoints .Elasticsearch ),
80+ WithUsername (project .Credentials .Username ),
81+ WithPassword (project .Credentials .Password ),
82+ )
5183 if err != nil {
52- return nil , err
84+ return err
5385 }
54- req , err := http .NewRequestWithContext (ctx , "POST" , url + "/api/v1/serverless/projects/" + projectType , bytes .NewReader (p ))
86+
87+ statusCode , respBody , err := client .get (ctx , "/_cluster/health" )
5588 if err != nil {
56- return nil , err
89+ return fmt .Errorf ("failed to query elasticsearch health: %w" , err )
90+ }
91+
92+ if statusCode != http .StatusOK {
93+ return fmt .Errorf ("unexpected status code %d, body: %s" , statusCode , string (respBody ))
5794 }
58- req .Header .Set ("Content-Type" , "application/json" )
59- req .Header .Set ("Authorization" , "ApiKey " + apiKey )
6095
61- resp , err := http .DefaultClient .Do (req )
96+ var health struct {
97+ Status string `json:"status"`
98+ }
99+ if err := json .Unmarshal (respBody , & health ); err != nil {
100+ logger .Debugf ("Unable to decode response: %v body: %s" , err , string (respBody ))
101+ return err
102+ }
103+ if health .Status == "green" {
104+ return nil
105+ }
106+ return fmt .Errorf ("elasticsearch unhealthy: %s" , health .Status )
107+ }
108+
109+ func getKibanaHealthy (ctx context.Context , project * Project ) error {
110+ client , err := NewClient (
111+ WithAddress (project .Endpoints .Kibana ),
112+ WithUsername (project .Credentials .Username ),
113+ WithPassword (project .Credentials .Password ),
114+ )
115+ if err != nil {
116+ return err
117+ }
118+
119+ statusCode , respBody , err := client .get (ctx , "/api/status" )
120+ if err != nil {
121+ return fmt .Errorf ("failed to query kibana status: %w" , err )
122+ }
123+ if statusCode != http .StatusOK {
124+ return fmt .Errorf ("unexpected status code %d, body: %s" , statusCode , string (respBody ))
125+ }
126+
127+ var status struct {
128+ Status struct {
129+ Overall struct {
130+ Level string `json:"level"`
131+ } `json:"overall"`
132+ } `json:"status"`
133+ }
134+ if err := json .Unmarshal (respBody , & status ); err != nil {
135+ logger .Debugf ("Unable to decode response: %v body: %s" , err , string (respBody ))
136+ return err
137+ }
138+ if status .Status .Overall .Level == "available" {
139+ return nil
140+ }
141+ return fmt .Errorf ("kibana unhealthy: %s" , status .Status .Overall .Level )
142+ }
143+
144+ func getFleetHealthy (ctx context.Context , project * Project ) error {
145+ client , err := NewClient (
146+ WithAddress (project .Endpoints .Fleet ),
147+ WithUsername (project .Credentials .Username ),
148+ WithPassword (project .Credentials .Password ),
149+ )
62150 if err != nil {
63- return nil , err
151+ return err
64152 }
65- defer resp .Body .Close ()
66153
67- if resp .StatusCode != http .StatusCreated {
68- p , _ := io .ReadAll (resp .Body )
69- return nil , fmt .Errorf ("unexpected status code %d, body: %s" , resp .StatusCode , string (p ))
154+ statusCode , respBody , err := client .get (ctx , "/api/status" )
155+ if err != nil {
156+ return fmt .Errorf ("failed to query fleet status: %w" , err )
157+ }
158+ if statusCode != http .StatusOK {
159+ return fmt .Errorf ("fleet unhealthy: status code %d, body: %s" , statusCode , string (respBody ))
70160 }
71- project := & Project {url : url , apiKey : apiKey }
72161
73- err = json .NewDecoder (resp .Body ).Decode (project )
74- return project , err
162+ return nil
75163}
0 commit comments