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
+
1
5
package serverless
2
6
3
7
import (
4
- "bytes"
5
8
"context"
6
9
"encoding/json"
7
10
"fmt"
8
- "io"
9
11
"net/http"
12
+ "time"
13
+
14
+ "github.com/elastic/elastic-package/internal/logger"
10
15
)
11
16
12
17
// Project represents a serverless project
@@ -16,6 +21,7 @@ type Project struct {
16
21
17
22
Name string `json:"name"`
18
23
ID string `json:"id"`
24
+ Alias string `json:"alias"`
19
25
Type string `json:"type"`
20
26
Region string `json:"region_id"`
21
27
@@ -32,44 +38,126 @@ type Project struct {
32
38
} `json:"endpoints"`
33
39
}
34
40
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
38
54
}
39
55
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
+ )
51
83
if err != nil {
52
- return nil , err
84
+ return err
53
85
}
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" )
55
88
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 ))
57
94
}
58
- req .Header .Set ("Content-Type" , "application/json" )
59
- req .Header .Set ("Authorization" , "ApiKey " + apiKey )
60
95
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
+ )
62
150
if err != nil {
63
- return nil , err
151
+ return err
64
152
}
65
- defer resp .Body .Close ()
66
153
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 ))
70
160
}
71
- project := & Project {url : url , apiKey : apiKey }
72
161
73
- err = json .NewDecoder (resp .Body ).Decode (project )
74
- return project , err
162
+ return nil
75
163
}
0 commit comments