Skip to content

Commit fef3786

Browse files
committed
Migrate to the API version 2
1 parent bc6501c commit fef3786

File tree

3 files changed

+141
-72
lines changed

3 files changed

+141
-72
lines changed

client/nginx_client.go

Lines changed: 127 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package client
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
7+
"io"
68
"io/ioutil"
79
"net/http"
810
)
911

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
1116
type NginxClient struct {
12-
upstreamConfEndpoint string
13-
statusEndpoint string
17+
apiEndpoint string
18+
httpClient *http.Client
1419
}
1520

1621
type peers struct {
@@ -22,88 +27,135 @@ type peer struct {
2227
Server string
2328
}
2429

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+
2555
// 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+
2859
if err != nil {
29-
return nil, err
60+
return nil, fmt.Errorf("error accessing the API: %v", err)
3061
}
3162

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)
3573
}
3674

37-
client := &NginxClient{upstreamConfEndpoint: upstreamConfEndpoint, statusEndpoint: statusEndpoint}
38-
return client, nil
75+
return &NginxClient{
76+
apiEndpoint: apiEndpoint,
77+
httpClient: httpClient,
78+
}, nil
3979
}
4080

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)
4383
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)
4585
}
4686
defer resp.Body.Close()
4787

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+
4892
body, err := ioutil.ReadAll(resp.Body)
4993
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)
5195
}
5296

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)
55101
}
56102

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)
61110
}
62111

63-
return nil
112+
return fmt.Errorf("%v; error: %v", mainErr, apiErr.toString())
64113
}
65114

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)
68117
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)
70119
}
71-
defer resp.Body.Close()
72120

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)
75125
}
76126

77-
return nil
127+
return &apiErr, nil
78128
}
79129

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.
81131
func (client *NginxClient) CheckIfUpstreamExists(upstream string) error {
82132
_, err := client.getUpstreamPeers(upstream)
83133
return err
84134
}
85135

86136
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)
88138

89-
resp, err := http.Get(request)
139+
resp, err := client.httpClient.Get(url)
90140
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)
92142
}
93143
defer resp.Body.Close()
94144

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)
97148
}
98149

99150
body, err := ioutil.ReadAll(resp.Body)
100151
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)
102153
}
154+
103155
var prs peers
104156
err = json.Unmarshal(body, &prs)
105157
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)
107159
}
108160

109161
return &prs, nil
@@ -114,22 +166,34 @@ func (client *NginxClient) AddHTTPServer(upstream string, server string) error {
114166
id, err := client.getIDOfHTTPServer(upstream, server)
115167

116168
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)
118170
}
119171
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,
121177
}
122178

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))
124187

125-
resp, err := http.Get(request)
126188
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)
128190
}
129191
defer resp.Body.Close()
130192

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)
133197
}
134198

135199
return nil
@@ -139,22 +203,29 @@ func (client *NginxClient) AddHTTPServer(upstream string, server string) error {
139203
func (client *NginxClient) DeleteHTTPServer(upstream string, server string) error {
140204
id, err := client.getIDOfHTTPServer(upstream, server)
141205
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)
143207
}
144208
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)
146210
}
147211

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)
149213

150-
resp, err := http.Get(request)
214+
req, err := http.NewRequest(http.MethodDelete, url, nil)
151215
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)
153222
}
154223
defer resp.Body.Close()
155224

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)
158229
}
159230

160231
return nil
@@ -166,22 +237,22 @@ func (client *NginxClient) DeleteHTTPServer(upstream string, server string) erro
166237
func (client *NginxClient) UpdateHTTPServers(upstream string, servers []string) ([]string, []string, error) {
167238
serversInNginx, err := client.GetHTTPServers(upstream)
168239
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)
170241
}
171242

172243
toAdd, toDelete := determineUpdates(servers, serversInNginx)
173244

174245
for _, server := range toAdd {
175246
err := client.AddHTTPServer(upstream, server)
176247
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)
178249
}
179250
}
180251

181252
for _, server := range toDelete {
182253
err := client.DeleteHTTPServer(upstream, server)
183254
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)
185256
}
186257
}
187258

@@ -222,7 +293,7 @@ func determineUpdates(updatedServers []string, nginxServers []string) (toAdd []s
222293
func (client *NginxClient) GetHTTPServers(upstream string) ([]string, error) {
223294
peers, err := client.getUpstreamPeers(upstream)
224295
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)
226297
}
227298

228299
var servers []string
@@ -236,7 +307,7 @@ func (client *NginxClient) GetHTTPServers(upstream string) ([]string, error) {
236307
func (client *NginxClient) getIDOfHTTPServer(upstream string, name string) (int, error) {
237308
peers, err := client.getUpstreamPeers(upstream)
238309
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)
240311
}
241312

242313
for _, p := range peers.Peers {

docker/test.conf

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,11 @@ upstream test {
55
server {
66
listen 8080;
77

8-
root /usr/share/nginx/html;
9-
10-
location = /status.html {
11-
}
12-
13-
location /status {
14-
status;
8+
location = /dashboard.html {
9+
root /usr/share/nginx/html;
1510
}
1611

17-
location /upstream_conf {
18-
upstream_conf;
12+
location /api {
13+
api write=on;
1914
}
2015
}

0 commit comments

Comments
 (0)