48
48
ErrServerExists = errors .New ("server already exists" )
49
49
ErrNotSupported = errors .New ("not supported" )
50
50
ErrInvalidTimeout = errors .New ("invalid timeout" )
51
+ ErrParameterMismatch = errors .New ("encountered duplicate server with different parameters" )
51
52
ErrPlusVersionNotFound = errors .New ("plus version not found in the input string" )
52
53
)
53
54
@@ -775,9 +776,13 @@ func (client *NginxClient) AddHTTPServer(ctx context.Context, upstream string, s
775
776
if id != - 1 {
776
777
return fmt .Errorf ("failed to add %v server to %v upstream: %w" , server .Server , upstream , ErrServerExists )
777
778
}
779
+ err = client .addHTTPServer (ctx , upstream , server )
780
+ return err
781
+ }
778
782
783
+ func (client * NginxClient ) addHTTPServer (ctx context.Context , upstream string , server UpstreamServer ) error {
779
784
path := fmt .Sprintf ("http/upstreams/%v/servers/" , upstream )
780
- err = client .post (ctx , path , & server )
785
+ err : = client .post (ctx , path , & server )
781
786
if err != nil {
782
787
return fmt .Errorf ("failed to add %v server to %v upstream: %w" , server .Server , upstream , err )
783
788
}
@@ -794,9 +799,13 @@ func (client *NginxClient) DeleteHTTPServer(ctx context.Context, upstream string
794
799
if id == - 1 {
795
800
return fmt .Errorf ("failed to remove %v server from %v upstream: %w" , server , upstream , ErrServerNotFound )
796
801
}
802
+ err = client .deleteHTTPServer (ctx , upstream , server , id )
803
+ return err
804
+ }
797
805
798
- path := fmt .Sprintf ("http/upstreams/%v/servers/%v" , upstream , id )
799
- err = client .delete (ctx , path , http .StatusOK )
806
+ func (client * NginxClient ) deleteHTTPServer (ctx context.Context , upstream , server string , serverID int ) error {
807
+ path := fmt .Sprintf ("http/upstreams/%v/servers/%v" , upstream , serverID )
808
+ err := client .delete (ctx , path , http .StatusOK )
800
809
if err != nil {
801
810
return fmt .Errorf ("failed to remove %v server from %v upstream: %w" , server , upstream , err )
802
811
}
@@ -809,6 +818,8 @@ func (client *NginxClient) DeleteHTTPServer(ctx context.Context, upstream string
809
818
// Servers that aren't in the slice, but exist in NGINX, will be removed from NGINX.
810
819
// Servers that are in the slice and exist in NGINX, but have different parameters, will be updated.
811
820
// The client will attempt to update all servers, returning all the errors that occurred.
821
+ // If there are duplicate servers with equivalent parameters, the duplicates will be ignored.
822
+ // If there are duplicate servers with different parameters, those server entries will be ignored and an error returned.
812
823
func (client * NginxClient ) UpdateHTTPServers (ctx context.Context , upstream string , servers []UpstreamServer ) (added []UpstreamServer , deleted []UpstreamServer , updated []UpstreamServer , err error ) {
813
824
serversInNginx , err := client .GetHTTPServers (ctx , upstream )
814
825
if err != nil {
@@ -822,10 +833,12 @@ func (client *NginxClient) UpdateHTTPServers(ctx context.Context, upstream strin
822
833
formattedServers = append (formattedServers , server )
823
834
}
824
835
836
+ formattedServers , err = deduplicateServers (upstream , formattedServers )
837
+
825
838
toAdd , toDelete , toUpdate := determineUpdates (formattedServers , serversInNginx )
826
839
827
840
for _ , server := range toAdd {
828
- addErr := client .AddHTTPServer (ctx , upstream , server )
841
+ addErr := client .addHTTPServer (ctx , upstream , server )
829
842
if addErr != nil {
830
843
err = errors .Join (err , addErr )
831
844
continue
@@ -834,7 +847,7 @@ func (client *NginxClient) UpdateHTTPServers(ctx context.Context, upstream strin
834
847
}
835
848
836
849
for _ , server := range toDelete {
837
- deleteErr := client .DeleteHTTPServer (ctx , upstream , server .Server )
850
+ deleteErr := client .deleteHTTPServer (ctx , upstream , server .Server , server . ID )
838
851
if deleteErr != nil {
839
852
err = errors .Join (err , deleteErr )
840
853
continue
@@ -858,46 +871,82 @@ func (client *NginxClient) UpdateHTTPServers(ctx context.Context, upstream strin
858
871
return added , deleted , updated , err
859
872
}
860
873
861
- // haveSameParameters checks if a given server has the same parameters as a server already present in NGINX. Order matters.
862
- func haveSameParameters (newServer UpstreamServer , serverNGX UpstreamServer ) bool {
863
- newServer .ID = serverNGX .ID
874
+ func deduplicateServers (upstream string , servers []UpstreamServer ) ([]UpstreamServer , error ) {
875
+ type serverCheck struct {
876
+ server UpstreamServer
877
+ valid bool
878
+ }
864
879
865
- if serverNGX .MaxConns != nil && newServer .MaxConns == nil {
866
- newServer .MaxConns = & defaultMaxConns
880
+ serverMap := make (map [string ]* serverCheck , len (servers ))
881
+ var err error
882
+ for _ , server := range servers {
883
+ if prev , ok := serverMap [server .Server ]; ok {
884
+ if ! prev .valid {
885
+ continue
886
+ }
887
+ if ! server .hasSameParametersAs (prev .server ) {
888
+ prev .valid = false
889
+ err = errors .Join (err , fmt .Errorf (
890
+ "failed to update %s server to %s upstream: %w" ,
891
+ server .Server , upstream , ErrParameterMismatch ))
892
+ }
893
+ continue
894
+ }
895
+ serverMap [server .Server ] = & serverCheck {server , true }
867
896
}
897
+ retServers := make ([]UpstreamServer , 0 , len (serverMap ))
898
+ for _ , server := range servers {
899
+ if check , ok := serverMap [server .Server ]; ok && check .valid {
900
+ retServers = append (retServers , server )
901
+ delete (serverMap , server .Server )
902
+ }
903
+ }
904
+ return retServers , err
905
+ }
868
906
869
- if serverNGX .MaxFails != nil && newServer .MaxFails == nil {
870
- newServer .MaxFails = & defaultMaxFails
907
+ // hasSameParametersAs checks if a given server has the same parameters.
908
+ func (s UpstreamServer ) hasSameParametersAs (compareServer UpstreamServer ) bool {
909
+ s .ID = compareServer .ID
910
+ s .applyDefaults ()
911
+ compareServer .applyDefaults ()
912
+ return reflect .DeepEqual (s , compareServer )
913
+ }
914
+
915
+ func (s * UpstreamServer ) applyDefaults () {
916
+ if s .MaxConns == nil {
917
+ s .MaxConns = & defaultMaxConns
871
918
}
872
919
873
- if serverNGX . FailTimeout != "" && newServer . FailTimeout == "" {
874
- newServer . FailTimeout = defaultFailTimeout
920
+ if s . MaxFails == nil {
921
+ s . MaxFails = & defaultMaxFails
875
922
}
876
923
877
- if serverNGX . SlowStart != "" && newServer . SlowStart == "" {
878
- newServer . SlowStart = defaultSlowStart
924
+ if s . FailTimeout == "" {
925
+ s . FailTimeout = defaultFailTimeout
879
926
}
880
927
881
- if serverNGX . Backup != nil && newServer . Backup == nil {
882
- newServer . Backup = & defaultBackup
928
+ if s . SlowStart == "" {
929
+ s . SlowStart = defaultSlowStart
883
930
}
884
931
885
- if serverNGX . Down != nil && newServer . Down == nil {
886
- newServer . Down = & defaultDown
932
+ if s . Backup == nil {
933
+ s . Backup = & defaultBackup
887
934
}
888
935
889
- if serverNGX . Weight != nil && newServer . Weight == nil {
890
- newServer . Weight = & defaultWeight
936
+ if s . Down == nil {
937
+ s . Down = & defaultDown
891
938
}
892
939
893
- return reflect .DeepEqual (newServer , serverNGX )
940
+ if s .Weight == nil {
941
+ s .Weight = & defaultWeight
942
+ }
894
943
}
895
944
896
945
func determineUpdates (updatedServers []UpstreamServer , nginxServers []UpstreamServer ) (toAdd []UpstreamServer , toRemove []UpstreamServer , toUpdate []UpstreamServer ) {
897
946
for _ , server := range updatedServers {
898
947
updateFound := false
899
948
for _ , serverNGX := range nginxServers {
900
- if server .Server == serverNGX .Server && ! haveSameParameters ( server , serverNGX ) {
949
+ if server .Server == serverNGX .Server && ! server . hasSameParametersAs ( serverNGX ) {
901
950
server .ID = serverNGX .ID
902
951
updateFound = true
903
952
break
@@ -1046,6 +1095,7 @@ func (client *NginxClient) patch(ctx context.Context, path string, input interfa
1046
1095
if err != nil {
1047
1096
return fmt .Errorf ("failed to create a patch request: %w" , err )
1048
1097
}
1098
+ req .Header .Set ("Content-Type" , "application/json" )
1049
1099
1050
1100
resp , err := client .httpClient .Do (req )
1051
1101
if err != nil {
@@ -1088,9 +1138,13 @@ func (client *NginxClient) AddStreamServer(ctx context.Context, upstream string,
1088
1138
if id != - 1 {
1089
1139
return fmt .Errorf ("failed to add %v stream server to %v upstream: %w" , server .Server , upstream , ErrServerExists )
1090
1140
}
1141
+ err = client .addStreamServer (ctx , upstream , server )
1142
+ return err
1143
+ }
1091
1144
1145
+ func (client * NginxClient ) addStreamServer (ctx context.Context , upstream string , server StreamUpstreamServer ) error {
1092
1146
path := fmt .Sprintf ("stream/upstreams/%v/servers/" , upstream )
1093
- err = client .post (ctx , path , & server )
1147
+ err : = client .post (ctx , path , & server )
1094
1148
if err != nil {
1095
1149
return fmt .Errorf ("failed to add %v stream server to %v upstream: %w" , server .Server , upstream , err )
1096
1150
}
@@ -1106,9 +1160,13 @@ func (client *NginxClient) DeleteStreamServer(ctx context.Context, upstream stri
1106
1160
if id == - 1 {
1107
1161
return fmt .Errorf ("failed to remove %v stream server from %v upstream: %w" , server , upstream , ErrServerNotFound )
1108
1162
}
1163
+ err = client .deleteStreamServer (ctx , upstream , server , id )
1164
+ return err
1165
+ }
1109
1166
1110
- path := fmt .Sprintf ("stream/upstreams/%v/servers/%v" , upstream , id )
1111
- err = client .delete (ctx , path , http .StatusOK )
1167
+ func (client * NginxClient ) deleteStreamServer (ctx context.Context , upstream , server string , serverID int ) error {
1168
+ path := fmt .Sprintf ("stream/upstreams/%v/servers/%v" , upstream , serverID )
1169
+ err := client .delete (ctx , path , http .StatusOK )
1112
1170
if err != nil {
1113
1171
return fmt .Errorf ("failed to remove %v stream server from %v upstream: %w" , server , upstream , err )
1114
1172
}
@@ -1120,6 +1178,8 @@ func (client *NginxClient) DeleteStreamServer(ctx context.Context, upstream stri
1120
1178
// Servers that aren't in the slice, but exist in NGINX, will be removed from NGINX.
1121
1179
// Servers that are in the slice and exist in NGINX, but have different parameters, will be updated.
1122
1180
// The client will attempt to update all servers, returning all the errors that occurred.
1181
+ // If there are duplicate servers with equivalent parameters, the duplicates will be ignored.
1182
+ // If there are duplicate servers with different parameters, those server entries will be ignored and an error returned.
1123
1183
func (client * NginxClient ) UpdateStreamServers (ctx context.Context , upstream string , servers []StreamUpstreamServer ) (added []StreamUpstreamServer , deleted []StreamUpstreamServer , updated []StreamUpstreamServer , err error ) {
1124
1184
serversInNginx , err := client .GetStreamServers (ctx , upstream )
1125
1185
if err != nil {
@@ -1132,10 +1192,12 @@ func (client *NginxClient) UpdateStreamServers(ctx context.Context, upstream str
1132
1192
formattedServers = append (formattedServers , server )
1133
1193
}
1134
1194
1195
+ formattedServers , err = deduplicateStreamServers (upstream , formattedServers )
1196
+
1135
1197
toAdd , toDelete , toUpdate := determineStreamUpdates (formattedServers , serversInNginx )
1136
1198
1137
1199
for _ , server := range toAdd {
1138
- addErr := client .AddStreamServer (ctx , upstream , server )
1200
+ addErr := client .addStreamServer (ctx , upstream , server )
1139
1201
if addErr != nil {
1140
1202
err = errors .Join (err , addErr )
1141
1203
continue
@@ -1144,7 +1206,7 @@ func (client *NginxClient) UpdateStreamServers(ctx context.Context, upstream str
1144
1206
}
1145
1207
1146
1208
for _ , server := range toDelete {
1147
- deleteErr := client .DeleteStreamServer (ctx , upstream , server .Server )
1209
+ deleteErr := client .deleteStreamServer (ctx , upstream , server .Server , server . ID )
1148
1210
if deleteErr != nil {
1149
1211
err = errors .Join (err , deleteErr )
1150
1212
continue
@@ -1183,45 +1245,82 @@ func (client *NginxClient) getIDOfStreamServer(ctx context.Context, upstream str
1183
1245
return - 1 , nil
1184
1246
}
1185
1247
1186
- // haveSameParametersForStream checks if a given server has the same parameters as a server already present in NGINX. Order matters.
1187
- func haveSameParametersForStream (newServer StreamUpstreamServer , serverNGX StreamUpstreamServer ) bool {
1188
- newServer .ID = serverNGX .ID
1189
- if serverNGX .MaxConns != nil && newServer .MaxConns == nil {
1190
- newServer .MaxConns = & defaultMaxConns
1248
+ func deduplicateStreamServers (upstream string , servers []StreamUpstreamServer ) ([]StreamUpstreamServer , error ) {
1249
+ type serverCheck struct {
1250
+ server StreamUpstreamServer
1251
+ valid bool
1191
1252
}
1192
1253
1193
- if serverNGX .MaxFails != nil && newServer .MaxFails == nil {
1194
- newServer .MaxFails = & defaultMaxFails
1254
+ serverMap := make (map [string ]* serverCheck , len (servers ))
1255
+ var err error
1256
+ for _ , server := range servers {
1257
+ if prev , ok := serverMap [server .Server ]; ok {
1258
+ if ! prev .valid {
1259
+ continue
1260
+ }
1261
+ if ! server .hasSameParametersAs (prev .server ) {
1262
+ prev .valid = false
1263
+ err = errors .Join (err , fmt .Errorf (
1264
+ "failed to update stream %s server to %s upstream: %w" ,
1265
+ server .Server , upstream , ErrParameterMismatch ))
1266
+ }
1267
+ continue
1268
+ }
1269
+ serverMap [server .Server ] = & serverCheck {server , true }
1195
1270
}
1271
+ retServers := make ([]StreamUpstreamServer , 0 , len (serverMap ))
1272
+ for _ , server := range servers {
1273
+ if check , ok := serverMap [server .Server ]; ok && check .valid {
1274
+ retServers = append (retServers , server )
1275
+ delete (serverMap , server .Server )
1276
+ }
1277
+ }
1278
+ return retServers , err
1279
+ }
1196
1280
1197
- if serverNGX .FailTimeout != "" && newServer .FailTimeout == "" {
1198
- newServer .FailTimeout = defaultFailTimeout
1281
+ // hasSameParametersAs checks if a given server has the same parameters.
1282
+ func (s StreamUpstreamServer ) hasSameParametersAs (compareServer StreamUpstreamServer ) bool {
1283
+ s .ID = compareServer .ID
1284
+ s .applyDefaults ()
1285
+ compareServer .applyDefaults ()
1286
+ return reflect .DeepEqual (s , compareServer )
1287
+ }
1288
+
1289
+ func (s * StreamUpstreamServer ) applyDefaults () {
1290
+ if s .MaxConns == nil {
1291
+ s .MaxConns = & defaultMaxConns
1292
+ }
1293
+
1294
+ if s .MaxFails == nil {
1295
+ s .MaxFails = & defaultMaxFails
1199
1296
}
1200
1297
1201
- if serverNGX . SlowStart != "" && newServer . SlowStart == "" {
1202
- newServer . SlowStart = defaultSlowStart
1298
+ if s . FailTimeout == "" {
1299
+ s . FailTimeout = defaultFailTimeout
1203
1300
}
1204
1301
1205
- if serverNGX . Backup != nil && newServer . Backup == nil {
1206
- newServer . Backup = & defaultBackup
1302
+ if s . SlowStart == "" {
1303
+ s . SlowStart = defaultSlowStart
1207
1304
}
1208
1305
1209
- if serverNGX . Down != nil && newServer . Down == nil {
1210
- newServer . Down = & defaultDown
1306
+ if s . Backup == nil {
1307
+ s . Backup = & defaultBackup
1211
1308
}
1212
1309
1213
- if serverNGX . Weight != nil && newServer . Weight == nil {
1214
- newServer . Weight = & defaultWeight
1310
+ if s . Down == nil {
1311
+ s . Down = & defaultDown
1215
1312
}
1216
1313
1217
- return reflect .DeepEqual (newServer , serverNGX )
1314
+ if s .Weight == nil {
1315
+ s .Weight = & defaultWeight
1316
+ }
1218
1317
}
1219
1318
1220
1319
func determineStreamUpdates (updatedServers []StreamUpstreamServer , nginxServers []StreamUpstreamServer ) (toAdd []StreamUpstreamServer , toRemove []StreamUpstreamServer , toUpdate []StreamUpstreamServer ) {
1221
1320
for _ , server := range updatedServers {
1222
1321
updateFound := false
1223
1322
for _ , serverNGX := range nginxServers {
1224
- if server .Server == serverNGX .Server && ! haveSameParametersForStream ( server , serverNGX ) {
1323
+ if server .Server == serverNGX .Server && ! server . hasSameParametersAs ( serverNGX ) {
1225
1324
server .ID = serverNGX .ID
1226
1325
updateFound = true
1227
1326
break
@@ -1949,9 +2048,13 @@ func (client *NginxClient) deleteKeyValPairs(ctx context.Context, zone string, s
1949
2048
return nil
1950
2049
}
1951
2050
1952
- // UpdateHTTPServer updates the server of the upstream.
2051
+ // UpdateHTTPServer updates the server of the upstream with the matching server ID .
1953
2052
func (client * NginxClient ) UpdateHTTPServer (ctx context.Context , upstream string , server UpstreamServer ) error {
1954
2053
path := fmt .Sprintf ("http/upstreams/%v/servers/%v" , upstream , server .ID )
2054
+ // The server ID is expected in the URI, but not expected in the body.
2055
+ // The NGINX API will return
2056
+ // {"error":{"status":400,"text":"unknown parameter \"id\"","code":"UpstreamConfFormatError"}
2057
+ // if the ID field is present.
1955
2058
server .ID = 0
1956
2059
err := client .patch (ctx , path , & server , http .StatusOK )
1957
2060
if err != nil {
@@ -1961,9 +2064,13 @@ func (client *NginxClient) UpdateHTTPServer(ctx context.Context, upstream string
1961
2064
return nil
1962
2065
}
1963
2066
1964
- // UpdateStreamServer updates the stream server of the upstream.
2067
+ // UpdateStreamServer updates the stream server of the upstream with the matching server ID .
1965
2068
func (client * NginxClient ) UpdateStreamServer (ctx context.Context , upstream string , server StreamUpstreamServer ) error {
1966
2069
path := fmt .Sprintf ("stream/upstreams/%v/servers/%v" , upstream , server .ID )
2070
+ // The server ID is expected in the URI, but not expected in the body.
2071
+ // The NGINX API will return
2072
+ // {"error":{"status":400,"text":"unknown parameter \"id\"","code":"UpstreamConfFormatError"}
2073
+ // if the ID field is present.
1967
2074
server .ID = 0
1968
2075
err := client .patch (ctx , path , & server , http .StatusOK )
1969
2076
if err != nil {
0 commit comments