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 "
11
+ "log "
9
12
"net/http"
13
+ "time"
14
+
15
+ "github.com/elastic/elastic-package/internal/logger"
10
16
)
11
17
12
18
// Project represents a serverless project
@@ -16,6 +22,7 @@ type Project struct {
16
22
17
23
Name string `json:"name"`
18
24
ID string `json:"id"`
25
+ Alias string `json:"alias"`
19
26
Type string `json:"type"`
20
27
Region string `json:"region_id"`
21
28
@@ -32,44 +39,126 @@ type Project struct {
32
39
} `json:"endpoints"`
33
40
}
34
41
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" )
42
+ type serviceHealthy func (context.Context , * Project ) error
43
+
44
+ func (p * Project ) EnsureHealthy (ctx context.Context ) error {
45
+ if err := p .ensureServiceHealthy (ctx , getESHealthy ); err != nil {
46
+ return fmt .Errorf ("elasticsearch not healthy: %w" , err )
47
+ }
48
+ if err := p .ensureServiceHealthy (ctx , getKibanaHealthy ); err != nil {
49
+ return fmt .Errorf ("kibana not healthy: %w" , err )
50
+ }
51
+ if err := p .ensureServiceHealthy (ctx , getFleetHealthy ); err != nil {
52
+ return fmt .Errorf ("fleet not healthy: %w" , err )
53
+ }
54
+ return nil
38
55
}
39
56
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 )
57
+ func (p * Project ) ensureServiceHealthy (ctx context.Context , serviceFunc serviceHealthy ) error {
58
+ timer := time .NewTimer (time .Millisecond )
59
+ for {
60
+ select {
61
+ case <- ctx .Done ():
62
+ return ctx .Err ()
63
+ case <- timer .C :
64
+ }
65
+
66
+ err := serviceFunc (ctx , p )
67
+ if err != nil {
68
+ logger .Debugf ("service not ready: %s" , err .Error ())
69
+ timer .Reset (time .Second * 5 )
70
+ continue
71
+ }
72
+
73
+ return nil
74
+ }
75
+ return nil
76
+ }
77
+
78
+ func getESHealthy (ctx context.Context , project * Project ) error {
79
+ client , err := NewClient (
80
+ WithAddress (project .Endpoints .Elasticsearch ),
81
+ WithUsername (project .Credentials .Username ),
82
+ WithPassword (project .Credentials .Password ),
83
+ )
51
84
if err != nil {
52
- return nil , err
85
+ return err
53
86
}
54
- req , err := http .NewRequestWithContext (ctx , "POST" , url + "/api/v1/serverless/projects/" + projectType , bytes .NewReader (p ))
87
+
88
+ statusCode , respBody , err := client .get (ctx , "/_cluster/health" )
55
89
if err != nil {
56
- return nil , err
90
+ return fmt .Errorf ("failed to query elasticsearch health: %w" , err )
91
+ }
92
+
93
+ if statusCode != http .StatusOK {
94
+ return fmt .Errorf ("unexpected status code %d, body: %s" , statusCode , string (respBody ))
57
95
}
58
- req .Header .Set ("Content-Type" , "application/json" )
59
- req .Header .Set ("Authorization" , "ApiKey " + apiKey )
60
96
61
- resp , err := http .DefaultClient .Do (req )
97
+ var health struct {
98
+ Status string `json:"status"`
99
+ }
100
+ if err := json .Unmarshal (respBody , & health ); err != nil {
101
+ log .Printf ("Unable to decode response: %v body: %s" , err , string (respBody ))
102
+ return err
103
+ }
104
+ if health .Status == "green" {
105
+ return nil
106
+ }
107
+ return fmt .Errorf ("elasticsearch unhealthy: %s" , health .Status )
108
+ }
109
+
110
+ func getKibanaHealthy (ctx context.Context , project * Project ) error {
111
+ client , err := NewClient (
112
+ WithAddress (project .Endpoints .Kibana ),
113
+ WithUsername (project .Credentials .Username ),
114
+ WithPassword (project .Credentials .Password ),
115
+ )
116
+ if err != nil {
117
+ return err
118
+ }
119
+
120
+ statusCode , respBody , err := client .get (ctx , "/api/status" )
121
+ if err != nil {
122
+ return fmt .Errorf ("failed to query kibana status: %w" , err )
123
+ }
124
+ if statusCode != http .StatusOK {
125
+ return fmt .Errorf ("unexpected status code %d, body: %s" , statusCode , string (respBody ))
126
+ }
127
+
128
+ var status struct {
129
+ Status struct {
130
+ Overall struct {
131
+ Level string `json:"level"`
132
+ } `json:"overall"`
133
+ } `json:"status"`
134
+ }
135
+ if err := json .Unmarshal (respBody , & status ); err != nil {
136
+ log .Printf ("Unable to decode response: %v body: %s" , err , string (respBody ))
137
+ return err
138
+ }
139
+ if status .Status .Overall .Level == "available" {
140
+ return nil
141
+ }
142
+ return fmt .Errorf ("kibana unhealthy: %s" , status .Status .Overall .Level )
143
+ }
144
+
145
+ func getFleetHealthy (ctx context.Context , project * Project ) error {
146
+ client , err := NewClient (
147
+ WithAddress (project .Endpoints .Fleet ),
148
+ WithUsername (project .Credentials .Username ),
149
+ WithPassword (project .Credentials .Password ),
150
+ )
62
151
if err != nil {
63
- return nil , err
152
+ return err
64
153
}
65
- defer resp .Body .Close ()
66
154
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 ))
155
+ statusCode , respBody , err := client .get (ctx , "/api/status" )
156
+ if err != nil {
157
+ return fmt .Errorf ("failed to query fleet status: %w" , err )
158
+ }
159
+ if statusCode != http .StatusOK {
160
+ return fmt .Errorf ("fleet unhealthy: status code %d, body: %s" , statusCode , string (respBody ))
70
161
}
71
- project := & Project {url : url , apiKey : apiKey }
72
162
73
- err = json .NewDecoder (resp .Body ).Decode (project )
74
- return project , err
163
+ return nil
75
164
}
0 commit comments