Skip to content

Commit 194a6b7

Browse files
author
isaac
committed
Add key-value support
Full support for key-value endpoints for both http and stream contexts.
1 parent 1900f9f commit 194a6b7

File tree

3 files changed

+452
-0
lines changed

3 files changed

+452
-0
lines changed

client/nginx.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ const APIVersion = 2
1414

1515
const streamNotConfiguredCode = "StreamNotConfigured"
1616

17+
// Stream is a stream context parameter.
18+
const Stream = true
19+
20+
// HTTP is a HTTP context parameter.
21+
const HTTP = false
22+
1723
// NginxClient lets you access NGINX Plus API.
1824
type NginxClient struct {
1925
apiEndpoint string
@@ -778,3 +784,244 @@ func (client *NginxClient) getStreamUpstreams() (*StreamUpstreams, error) {
778784
}
779785
return &upstreams, nil
780786
}
787+
788+
// KeyValPairs are the key-value pairs stored in a zone.
789+
type KeyValPairs map[string]string
790+
791+
// KeyValPairsByZone are the KeyValPairs for all zones, by zone name.
792+
type KeyValPairsByZone map[string]KeyValPairs
793+
794+
// GetKeyValPairs fetches key/value pairs for a given zone.
795+
func (client *NginxClient) GetKeyValPairs(zone string) (KeyValPairs, error) {
796+
return client.getKeyValPairs(zone, HTTP)
797+
}
798+
799+
// GetStreamKeyValPairs fetches key/value pairs for a given zone.
800+
func (client *NginxClient) GetStreamKeyValPairs(zone string) (KeyValPairs, error) {
801+
return client.getKeyValPairs(zone, Stream)
802+
}
803+
804+
func (client *NginxClient) getKeyValPairs(zone string, stream bool) (KeyValPairs, error) {
805+
keyvals := make(map[string]string)
806+
base := "http"
807+
if stream {
808+
base = "stream"
809+
}
810+
if zone == "" {
811+
return nil, fmt.Errorf("zone required")
812+
}
813+
err := client.get(fmt.Sprintf("%v/keyvals/%v", base, zone), &keyvals)
814+
if err != nil {
815+
return nil, fmt.Errorf("failed to get keyvals for zone: %v/%v: %v", base, zone, err)
816+
}
817+
return keyvals, nil
818+
}
819+
820+
// GetAllKeyValPairs fetches all key/value pairs for all zones.
821+
func (client *NginxClient) GetAllKeyValPairs() (KeyValPairsByZone, error) {
822+
return client.getAllKeyValPairs(HTTP)
823+
}
824+
825+
// GetStreamAllKeyValPairs fetches all key/value pairs for all zones.
826+
func (client *NginxClient) GetStreamAllKeyValPairs() (KeyValPairsByZone, error) {
827+
return client.getAllKeyValPairs(Stream)
828+
}
829+
830+
func (client *NginxClient) getAllKeyValPairs(stream bool) (KeyValPairsByZone, error) {
831+
// keyvals := make(map[string]map[string]string)
832+
var keyvals KeyValPairsByZone
833+
base := "http"
834+
if stream {
835+
base = "stream"
836+
}
837+
err := client.get(fmt.Sprintf("%v/keyvals", base), &keyvals)
838+
if err != nil {
839+
return nil, fmt.Errorf("failed to get keyvals for all %v zones: %v", base, err)
840+
}
841+
return keyvals, nil
842+
}
843+
844+
// AddKeyValPair adds a new key/value pair.
845+
func (client *NginxClient) AddKeyValPair(zone string, keyval KeyValPairs) error {
846+
return client.addKeyValPair(zone, keyval, HTTP)
847+
}
848+
849+
// AddStreamKeyValPair adds a new key/value pair.
850+
func (client *NginxClient) AddStreamKeyValPair(zone string, keyval KeyValPairs) error {
851+
return client.addKeyValPair(zone, keyval, Stream)
852+
}
853+
854+
func (client *NginxClient) addKeyValPair(zone string, keyval KeyValPairs, stream bool) error {
855+
base := "http"
856+
if stream {
857+
base = "stream"
858+
}
859+
path := fmt.Sprintf("%v/keyvals", base)
860+
url := fmt.Sprintf("%v/%v/%v", client.apiEndpoint, APIVersion, path)
861+
if zone != "" {
862+
url = fmt.Sprintf("%v/%v", url, zone)
863+
} else {
864+
return fmt.Errorf("zone required")
865+
}
866+
867+
jsonInput, err := json.Marshal(keyval)
868+
if err != nil {
869+
return fmt.Errorf("failed to marshall input: %v", err)
870+
}
871+
872+
resp, err := client.httpClient.Post(url, "application/json", bytes.NewBuffer(jsonInput))
873+
if err != nil {
874+
return fmt.Errorf("failed to create post request: %v", err)
875+
}
876+
defer resp.Body.Close()
877+
878+
if resp.StatusCode != http.StatusCreated {
879+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
880+
"expected %v response, got %v",
881+
http.StatusCreated, resp.StatusCode))
882+
}
883+
return nil
884+
}
885+
886+
// ModifyKeyValPair modifies the value of an existing key.
887+
func (client *NginxClient) ModifyKeyValPair(zone string, keyval KeyValPairs) error {
888+
return client.modifyKeyValPair(zone, keyval, HTTP)
889+
}
890+
891+
// ModifyStreamKeyValPair modifies the value of an existing key.
892+
func (client *NginxClient) ModifyStreamKeyValPair(zone string, keyval KeyValPairs) error {
893+
return client.modifyKeyValPair(zone, keyval, Stream)
894+
}
895+
896+
func (client *NginxClient) modifyKeyValPair(zone string, keyval KeyValPairs, stream bool) error {
897+
base := "http"
898+
if stream {
899+
base = "stream"
900+
}
901+
path := fmt.Sprintf("%v/keyvals", base)
902+
url := fmt.Sprintf("%v/%v/%v", client.apiEndpoint, APIVersion, path)
903+
if zone != "" {
904+
url = fmt.Sprintf("%v/%v", url, zone)
905+
} else {
906+
return fmt.Errorf("zone required")
907+
}
908+
909+
jsonInput, err := json.Marshal(keyval)
910+
if err != nil {
911+
return fmt.Errorf("failed to marshall input: %v", err)
912+
}
913+
req, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(jsonInput))
914+
if err != nil {
915+
return fmt.Errorf("failed to create a patch request: %v", err)
916+
}
917+
req.Header.Set("Content-Type", "application/json")
918+
919+
resp, err := client.httpClient.Do(req)
920+
if err != nil {
921+
return fmt.Errorf("failed to do patch request: %v", err)
922+
}
923+
defer resp.Body.Close()
924+
// We will consider ONLY 204 as success
925+
if resp.StatusCode != http.StatusNoContent {
926+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
927+
"expected %v response, got %v",
928+
http.StatusNoContent, resp.StatusCode))
929+
}
930+
return nil
931+
}
932+
933+
// DeleteKeyValuePair deletes the key/value pair for a given key.
934+
func (client *NginxClient) DeleteKeyValuePair(zone string, key string) error {
935+
return client.deleteKeyValuePair(zone, key, HTTP)
936+
}
937+
938+
// DeleteStreamKeyValuePair deletes the key/value pair for a given key.
939+
func (client *NginxClient) DeleteStreamKeyValuePair(zone string, key string) error {
940+
return client.deleteKeyValuePair(zone, key, Stream)
941+
}
942+
943+
// To delete a key/value pair you set the value to null via the API,
944+
// then NGINX+ will delete the key.
945+
func (client *NginxClient) deleteKeyValuePair(zone string, key string, stream bool) error {
946+
base := "http"
947+
if stream {
948+
base = "stream"
949+
}
950+
path := fmt.Sprintf("%v/keyvals", base)
951+
url := fmt.Sprintf("%v/%v/%v", client.apiEndpoint, APIVersion, path)
952+
if zone != "" {
953+
url = fmt.Sprintf("%v/%v", url, zone)
954+
} else {
955+
return fmt.Errorf("zone required")
956+
}
957+
958+
// map[string]string can't have a nil value so we use a different type here.
959+
keyval := make(map[string]interface{})
960+
keyval[key] = nil
961+
962+
jsonInput, err := json.Marshal(keyval)
963+
if err != nil {
964+
return fmt.Errorf("failed to marshall input: %v", err)
965+
}
966+
967+
req, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(jsonInput))
968+
if err != nil {
969+
return fmt.Errorf("failed to create a patch request: %v", err)
970+
}
971+
req.Header.Set("Content-Type", "application/json")
972+
973+
resp, err := client.httpClient.Do(req)
974+
if err != nil {
975+
return fmt.Errorf("failed to do patch request: %v", err)
976+
}
977+
defer resp.Body.Close()
978+
979+
// Expect status 204
980+
if resp.StatusCode != http.StatusNoContent {
981+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
982+
"expected %v response, got %v",
983+
http.StatusNoContent, resp.StatusCode))
984+
}
985+
return nil
986+
}
987+
988+
// DeleteKeyValuePairs deletes all the key-value pairs in a given zone.
989+
func (client *NginxClient) DeleteKeyValuePairs(zone string) error {
990+
return client.deleteKeyValPairs(zone, HTTP)
991+
}
992+
993+
// DeleteStreamKeyValuePairs deletes all the key-value pairs in a given zone.
994+
func (client *NginxClient) DeleteStreamKeyValuePairs(zone string) error {
995+
return client.deleteKeyValPairs(zone, Stream)
996+
}
997+
998+
func (client *NginxClient) deleteKeyValPairs(zone string, stream bool) error {
999+
base := "http"
1000+
if stream {
1001+
base = "stream"
1002+
}
1003+
if zone == "" {
1004+
return fmt.Errorf("zone required")
1005+
}
1006+
path := fmt.Sprintf("%v/keyvals/%v", base, zone)
1007+
url := fmt.Sprintf("%v/%v/%v", client.apiEndpoint, APIVersion, path)
1008+
1009+
req, err := http.NewRequest(http.MethodDelete, url, nil)
1010+
if err != nil {
1011+
return fmt.Errorf("failed to create a delete request: %v", err)
1012+
}
1013+
1014+
resp, err := client.httpClient.Do(req)
1015+
if err != nil {
1016+
return fmt.Errorf("failed to do delete request: %v", err)
1017+
}
1018+
defer resp.Body.Close()
1019+
1020+
// expect status 204
1021+
if resp.StatusCode != http.StatusNoContent {
1022+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
1023+
"expected %v response, got %v",
1024+
http.StatusNoContent, resp.StatusCode))
1025+
}
1026+
return nil
1027+
}

docker/nginx.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@ http {
2828

2929
#gzip on;
3030

31+
keyval_zone zone=zone_one:32k;
32+
keyval $arg_text $text zone=zone_one;
33+
3134
include /etc/nginx/conf.d/*.conf;
3235
}
3336

3437
stream {
38+
keyval_zone zone=zone_one_stream:32k;
39+
keyval $hostname $text zone=zone_one_stream;
40+
3541
upstream stream_test {
3642
zone stream_test 64k;
3743
}

0 commit comments

Comments
 (0)