11package plus
22
33import (
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
1116type NginxClient struct {
12- upstreamConfEndpoint string
13- statusEndpoint string
17+ apiEndpoint string
18+ httpClient * http. Client
1419}
1520
1621type 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.
81131func (client * NginxClient ) CheckIfUpstreamExists (upstream string ) error {
82132 _ , err := client .getUpstreamPeers (upstream )
83133 return err
84134}
85135
86136func (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 {
139203func (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
166237func (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
222293func (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) {
236307func (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 {
0 commit comments