1
1
package plus
2
2
3
3
import (
4
+ "bytes"
4
5
"encoding/json"
5
6
"fmt"
7
+ "io"
6
8
"io/ioutil"
7
9
"net/http"
8
10
)
9
11
10
- // NginxClient lets you add/remove servers to/from NGINX Plus via its upstream_conf API
12
+ // APIVersion is a version of NGINX Plus API
13
+ const APIVersion = 2
14
+
15
+ // NginxClient lets you add/remove servers to/from NGINX Plus via its API
11
16
type NginxClient struct {
12
- upstreamConfEndpoint string
13
- statusEndpoint string
17
+ apiEndpoint string
18
+ httpClient * http. Client
14
19
}
15
20
16
21
type peers struct {
@@ -22,88 +27,135 @@ type peer struct {
22
27
Server string
23
28
}
24
29
30
+ type versions []int
31
+
32
+ type upstreamServer struct {
33
+ Server string `json:"server"`
34
+ }
35
+
36
+ type apiErrorResponse struct {
37
+ Path string
38
+ Method string
39
+ Error apiError
40
+ RequestID string `json:"request_id"`
41
+ Href string
42
+ }
43
+
44
+ func (resp * apiErrorResponse ) toString () string {
45
+ return fmt .Sprintf ("path=%v; method=%v; error.status=%v; error.text=%v; error.code=%v; request_id=%v; href=%v" ,
46
+ resp .Path , resp .Method , resp .Error .Status , resp .Error .Text , resp .Error .Code , resp .RequestID , resp .Href )
47
+ }
48
+
49
+ type apiError struct {
50
+ Status int
51
+ Text string
52
+ Code string
53
+ }
54
+
25
55
// NewNginxClient creates an NginxClient.
26
- func NewNginxClient (upstreamConfEndpoint string , statusEndpoint string ) (* NginxClient , error ) {
27
- err := checkIfUpstreamConfIsAccessible (upstreamConfEndpoint )
56
+ func NewNginxClient (httpClient * http.Client , apiEndpoint string ) (* NginxClient , error ) {
57
+ versions , err := getAPIVersions (httpClient , apiEndpoint )
58
+
28
59
if err != nil {
29
- return nil , err
60
+ return nil , fmt . Errorf ( "error accessing the API: %v" , err )
30
61
}
31
62
32
- err = checkIfStatusIsAccessible (statusEndpoint )
33
- if err != nil {
34
- return nil , err
63
+ found := false
64
+ for _ , v := range * versions {
65
+ if v == APIVersion {
66
+ found = true
67
+ break
68
+ }
69
+ }
70
+
71
+ if ! found {
72
+ return nil , fmt .Errorf ("API version %v of the client is not supported by API versions of NGINX Plus: %v" , APIVersion , * versions )
35
73
}
36
74
37
- client := & NginxClient {upstreamConfEndpoint : upstreamConfEndpoint , statusEndpoint : statusEndpoint }
38
- return client , nil
75
+ return & NginxClient {
76
+ apiEndpoint : apiEndpoint ,
77
+ httpClient : httpClient ,
78
+ }, nil
39
79
}
40
80
41
- func checkIfUpstreamConfIsAccessible ( endpoint string ) error {
42
- resp , err := http .Get (endpoint )
81
+ func getAPIVersions ( httpClient * http. Client , endpoint string ) ( * versions , error ) {
82
+ resp , err := httpClient .Get (endpoint )
43
83
if err != nil {
44
- return fmt .Errorf ("upstream_conf endpoint %v is not accessible: %v" , endpoint , err )
84
+ return nil , fmt .Errorf ("%v is not accessible: %v" , endpoint , err )
45
85
}
46
86
defer resp .Body .Close ()
47
87
88
+ if resp .StatusCode != http .StatusOK {
89
+ return nil , fmt .Errorf ("%v is not accessible: expected %v response, got %v" , endpoint , http .StatusOK , resp .StatusCode )
90
+ }
91
+
48
92
body , err := ioutil .ReadAll (resp .Body )
49
93
if err != nil {
50
- return fmt .Errorf ("upstream_conf endpoint %v is not accessible : %v" , endpoint , err )
94
+ return nil , fmt .Errorf ("error while reading body of the response : %v" , err )
51
95
}
52
96
53
- if resp .StatusCode != http .StatusBadRequest {
54
- return fmt .Errorf ("upstream_conf endpoint %v is not accessible: expected 400 response, got %v" , endpoint , resp .StatusCode )
97
+ var vers versions
98
+ err = json .Unmarshal (body , & vers )
99
+ if err != nil {
100
+ return nil , fmt .Errorf ("error unmarshalling versions, got %q response: %v" , string (body ), err )
55
101
}
56
102
57
- bodyStr := string (body )
58
- expected := "missing \" upstream\" argument\n "
59
- if bodyStr != expected {
60
- return fmt .Errorf ("upstream_conf endpoint %v is not accessible: expected %q body, got %q" , endpoint , expected , bodyStr )
103
+ return & vers , nil
104
+ }
105
+
106
+ func createResponseMismatchError (respBody io.ReadCloser , mainErr error ) error {
107
+ apiErr , err := readAPIErrorResponse (respBody )
108
+ if err != nil {
109
+ return fmt .Errorf ("%v; failed to read the response body: %v" , mainErr , err )
61
110
}
62
111
63
- return nil
112
+ return fmt . Errorf ( "%v; error: %v" , mainErr , apiErr . toString ())
64
113
}
65
114
66
- func checkIfStatusIsAccessible ( endpoint string ) error {
67
- resp , err := http . Get ( endpoint )
115
+ func readAPIErrorResponse ( respBody io. ReadCloser ) ( * apiErrorResponse , error ) {
116
+ body , err := ioutil . ReadAll ( respBody )
68
117
if err != nil {
69
- return fmt .Errorf ("status endpoint is %v not accessible : %v" , endpoint , err )
118
+ return nil , fmt .Errorf ("failed to read the response body : %v" , err )
70
119
}
71
- defer resp .Body .Close ()
72
120
73
- if resp .StatusCode != http .StatusOK {
74
- return fmt .Errorf ("status endpoint is %v not accessible: expected 200 response, got %v" , endpoint , resp .StatusCode )
121
+ var apiErr apiErrorResponse
122
+ err = json .Unmarshal (body , & apiErr )
123
+ if err != nil {
124
+ return nil , fmt .Errorf ("error unmarshalling apiErrorResponse: got %q response: %v" , string (body ), err )
75
125
}
76
126
77
- return nil
127
+ return & apiErr , nil
78
128
}
79
129
80
- // CheckIfUpstreamExists checks if the upstream exists in NGINX. If the upstream doesn't exist, it returns an error.
130
+ // CheckIfUpstreamExists checks if the upstream exists in NGINX. If the upstream doesn't exist, it returns the error.
81
131
func (client * NginxClient ) CheckIfUpstreamExists (upstream string ) error {
82
132
_ , err := client .getUpstreamPeers (upstream )
83
133
return err
84
134
}
85
135
86
136
func (client * NginxClient ) getUpstreamPeers (upstream string ) (* peers , error ) {
87
- request := fmt .Sprintf ("%v/upstreams/%v" , client .statusEndpoint , upstream )
137
+ url := fmt .Sprintf ("%v/%v/http/ upstreams/%v" , client .apiEndpoint , APIVersion , upstream )
88
138
89
- resp , err := http . Get (request )
139
+ resp , err := client . httpClient . Get (url )
90
140
if err != nil {
91
- return nil , fmt .Errorf ("Failed to connect to the status api to get upstream %v info: %v" , upstream , err )
141
+ return nil , fmt .Errorf ("failed to connect to the API to get upstream %v info: %v" , upstream , err )
92
142
}
93
143
defer resp .Body .Close ()
94
144
95
- if resp .StatusCode == http .StatusNotFound {
96
- return nil , fmt .Errorf ("Upstream %v is not found" , upstream )
145
+ if resp .StatusCode != http .StatusOK {
146
+ mainErr := fmt .Errorf ("upstream %v is invalid: expected %v response, got %v" , upstream , http .StatusOK , resp .StatusCode )
147
+ return nil , createResponseMismatchError (resp .Body , mainErr )
97
148
}
98
149
99
150
body , err := ioutil .ReadAll (resp .Body )
100
151
if err != nil {
101
- return nil , fmt .Errorf ("Failed to read the response body with upstream %v info: %v" , upstream , err )
152
+ return nil , fmt .Errorf ("failed to read the response body with upstream %v info: %v" , upstream , err )
102
153
}
154
+
103
155
var prs peers
104
156
err = json .Unmarshal (body , & prs )
105
157
if err != nil {
106
- return nil , fmt .Errorf ("Error unmarshaling upstream %v: got %q response: %v" , upstream , string (body ), err )
158
+ return nil , fmt .Errorf ("error unmarshalling upstream %v: got %q response: %v" , upstream , string (body ), err )
107
159
}
108
160
109
161
return & prs , nil
@@ -114,22 +166,34 @@ func (client *NginxClient) AddHTTPServer(upstream string, server string) error {
114
166
id , err := client .getIDOfHTTPServer (upstream , server )
115
167
116
168
if err != nil {
117
- return fmt .Errorf ("Failed to add %v server to %v upstream: %v" , server , upstream , err )
169
+ return fmt .Errorf ("failed to add %v server to %v upstream: %v" , server , upstream , err )
118
170
}
119
171
if id != - 1 {
120
- return fmt .Errorf ("Failed to add %v server to %v upstream: server already exists" , server , upstream )
172
+ return fmt .Errorf ("failed to add %v server to %v upstream: server already exists" , server , upstream )
173
+ }
174
+
175
+ upsServer := upstreamServer {
176
+ Server : server ,
121
177
}
122
178
123
- request := fmt .Sprintf ("%v?upstream=%v&add=&server=%v" , client .upstreamConfEndpoint , upstream , server )
179
+ jsonServer , err := json .Marshal (upsServer )
180
+ if err != nil {
181
+ return fmt .Errorf ("error marshalling upstream server %v: %v" , upsServer , err )
182
+ }
183
+
184
+ url := fmt .Sprintf ("%v/%v/http/upstreams/%v/servers/" , client .apiEndpoint , APIVersion , upstream )
185
+
186
+ resp , err := client .httpClient .Post (url , "application/json" , bytes .NewBuffer (jsonServer ))
124
187
125
- resp , err := http .Get (request )
126
188
if err != nil {
127
- return fmt .Errorf ("Failed to add %v server to %v upstream: %v" , server , upstream , err )
189
+ return fmt .Errorf ("failed to add %v server to %v upstream: %v" , server , upstream , err )
128
190
}
129
191
defer resp .Body .Close ()
130
192
131
- if resp .StatusCode != http .StatusOK {
132
- return fmt .Errorf ("Failed to add %v server to %v upstream: expected 200 response, got %v" , server , upstream , resp .StatusCode )
193
+ if resp .StatusCode != http .StatusCreated {
194
+ mainErr := fmt .Errorf ("failed to add %v server to %v upstream: expected %v response, got %v" ,
195
+ server , upstream , http .StatusCreated , resp .StatusCode )
196
+ return createResponseMismatchError (resp .Body , mainErr )
133
197
}
134
198
135
199
return nil
@@ -139,22 +203,29 @@ func (client *NginxClient) AddHTTPServer(upstream string, server string) error {
139
203
func (client * NginxClient ) DeleteHTTPServer (upstream string , server string ) error {
140
204
id , err := client .getIDOfHTTPServer (upstream , server )
141
205
if err != nil {
142
- return fmt .Errorf ("Failed to remove %v server from %v upstream: %v" , server , upstream , err )
206
+ return fmt .Errorf ("failed to remove %v server from %v upstream: %v" , server , upstream , err )
143
207
}
144
208
if id == - 1 {
145
- return fmt .Errorf ("Failed to remove %v server from %v upstream: server doesn't exists" , server , upstream )
209
+ return fmt .Errorf ("failed to remove %v server from %v upstream: server doesn't exists" , server , upstream )
146
210
}
147
211
148
- request := fmt .Sprintf ("%v?upstream=%v&remove=&id=%v " , client .upstreamConfEndpoint , upstream , id )
212
+ url := fmt .Sprintf ("%v/%v/http/upstreams/%v/servers/%v " , client .apiEndpoint , APIVersion , upstream , id )
149
213
150
- resp , err := http .Get ( request )
214
+ req , err := http .NewRequest ( http . MethodDelete , url , nil )
151
215
if err != nil {
152
- return fmt .Errorf ("Failed to remove %v server from %v upstream: %v" , server , upstream , err )
216
+ return fmt .Errorf ("failed to create a request: %v" , err )
217
+ }
218
+
219
+ resp , err := client .httpClient .Do (req )
220
+ if err != nil {
221
+ return fmt .Errorf ("failed to remove %v server from %v upstream: %v" , server , upstream , err )
153
222
}
154
223
defer resp .Body .Close ()
155
224
156
- if resp .StatusCode != http .StatusOK && resp .StatusCode != http .StatusNoContent {
157
- return fmt .Errorf ("Failed to add %v server to %v upstream: expected 200 or 204 response, got %v" , server , upstream , resp .StatusCode )
225
+ if resp .StatusCode != http .StatusOK {
226
+ mainErr := fmt .Errorf ("failed to remove %v server from %v upstream: expected %v response, got %v" ,
227
+ server , upstream , http .StatusOK , resp .StatusCode )
228
+ return createResponseMismatchError (resp .Body , mainErr )
158
229
}
159
230
160
231
return nil
@@ -166,22 +237,22 @@ func (client *NginxClient) DeleteHTTPServer(upstream string, server string) erro
166
237
func (client * NginxClient ) UpdateHTTPServers (upstream string , servers []string ) ([]string , []string , error ) {
167
238
serversInNginx , err := client .GetHTTPServers (upstream )
168
239
if err != nil {
169
- return nil , nil , fmt .Errorf ("Failed to update servers of %v upstream: %v" , upstream , err )
240
+ return nil , nil , fmt .Errorf ("failed to update servers of %v upstream: %v" , upstream , err )
170
241
}
171
242
172
243
toAdd , toDelete := determineUpdates (servers , serversInNginx )
173
244
174
245
for _ , server := range toAdd {
175
246
err := client .AddHTTPServer (upstream , server )
176
247
if err != nil {
177
- return nil , nil , fmt .Errorf ("Failed to update servers of %v upstream: %v" , upstream , err )
248
+ return nil , nil , fmt .Errorf ("failed to update servers of %v upstream: %v" , upstream , err )
178
249
}
179
250
}
180
251
181
252
for _ , server := range toDelete {
182
253
err := client .DeleteHTTPServer (upstream , server )
183
254
if err != nil {
184
- return nil , nil , fmt .Errorf ("Failed to update servers of %v upstream: %v" , upstream , err )
255
+ return nil , nil , fmt .Errorf ("failed to update servers of %v upstream: %v" , upstream , err )
185
256
}
186
257
}
187
258
@@ -222,7 +293,7 @@ func determineUpdates(updatedServers []string, nginxServers []string) (toAdd []s
222
293
func (client * NginxClient ) GetHTTPServers (upstream string ) ([]string , error ) {
223
294
peers , err := client .getUpstreamPeers (upstream )
224
295
if err != nil {
225
- return nil , fmt .Errorf ("Error getting servers of %v upstream: %v" , upstream , err )
296
+ return nil , fmt .Errorf ("error getting servers of %v upstream: %v" , upstream , err )
226
297
}
227
298
228
299
var servers []string
@@ -236,7 +307,7 @@ func (client *NginxClient) GetHTTPServers(upstream string) ([]string, error) {
236
307
func (client * NginxClient ) getIDOfHTTPServer (upstream string , name string ) (int , error ) {
237
308
peers , err := client .getUpstreamPeers (upstream )
238
309
if err != nil {
239
- return - 1 , fmt .Errorf ("Error getting id of server %v of upstream %v: %v" , name , upstream , err )
310
+ return - 1 , fmt .Errorf ("error getting id of server %v of upstream %v: %v" , name , upstream , err )
240
311
}
241
312
242
313
for _ , p := range peers .Peers {
0 commit comments