From 0d2621b62f47c03e398fa353b962a92f84d80dac Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Mon, 10 Oct 2022 14:28:20 +0100 Subject: [PATCH 1/2] Create default NGINX API Client with specified Timeout --- .gitignore | 3 ++ Makefile | 10 ++++- README.md | 69 ++++++++++++++++++++++++++++++--- client/nginx.go | 54 ++++++++++++++++++++++++++ client/nginx_test.go | 62 +++++++++++++++++++++++++++++ examples/default-client/main.go | 40 +++++++++++++++++++ go.mod | 2 + go.sum | 2 + 8 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 examples/default-client/main.go create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 7b57f9e7..0d39dfa3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ .vscode dist + +coverage.out + diff --git a/Makefile b/Makefile index a33a4e8e..6477eefa 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,15 @@ export TEST_API_ENDPOINT=http://$(DOCKER_NGINX_PLUS):8080/api export TEST_API_ENDPOINT_OF_HELPER=http://$(DOCKER_NGINX_PLUS_HELPER):8080/api export TEST_UNAVAILABLE_STREAM_ADDRESS=$(DOCKER_NGINX_PLUS):8081 -test: run-nginx-plus test-run configure-no-stream-block test-run-no-stream-block clean + +test: run-nginx-plus test-run configure-no-stream-block test-run-no-stream-block clean ## Run integration tests + +unittest: + go test client/*.go -shuffle=on -race -v + +cover: + go test -shuffle=on -race -v client/*.go -count=1 -cover -covermode=atomic -coverprofile=coverage.out + go tool cover -html coverage.out lint: docker run --pull always --rm -v $(shell pwd):/nginx-plus-go-client -w /nginx-plus-go-client -v $(shell go env GOCACHE):/cache/go -e GOCACHE=/cache/go -e GOLANGCI_LINT_CACHE=/cache/go -v $(shell go env GOPATH)/pkg:/go/pkg golangci/golangci-lint:latest golangci-lint --color always run diff --git a/README.md b/README.md index 8909a8d2..394e2e93 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,76 @@ This Client works against versions 4 to 8 of the NGINX Plus API. The table below | 7 | R25 | | 8 | R27 | -## Using the Client +## Using the client library + +Import the library using: +```go +import "github.com/nginxinc/nginx-plus-go-client/client" +``` + +## Creating a client + +Create a new ```Client``` that works with the latest NGINX API Version: +```go +myHTTPClient := &http.Client{ + Timeout: 5*time.Second +} +c, err := NewNginxClient(myHTTPClient, "your-api-endpoint") +if err != nil { + // handle error +} +``` + +Create a new ```Client``` with specified API version: +```go +myHTTPClient := &http.Client{ + Timeout: 5*time.Second +} + +c, err := NewNginxClientWithVersion(myHTTPClient, "your-api-endpoint", 7) +if err != nil { + // handle error +} +``` -1. Import `github.com/nginxinc/nginx-plus-go-client/client` into your go project. -2. Use your favorite vendor tool to add this to your `/vendor` directory in your project. + + +Create a new default ```Client```: +```go +c, err := client.NewDefaultNginxClient("your-api-endpoint") +if err != nil { + // handle error +} +``` +Create a new default client with customized http.Client and API Version: +```go +myHTTPClient := &http.Client{ + Timeout: 60 * time.Second, +} + +c, err := client.NewDefaultNginxClient( + "your-api-endpoint", + client.WithHTTPClient(myHTTPClient), + client.WithAPIVersion(7), +) +if err != nil { + // handle error +} +``` +Note that: +- default NGINX Plus Client is using ```http.Client``` with specified ```Timeout``` value of 10s +- it is user's responsibility to provide correct NGINX API version ## Testing ### Unit tests +Run unittests +``` +$ make unittest +``` +Run unittests with coverage report ``` -$ cd client -$ go test +$ make cover ``` ### Integration tests diff --git a/client/nginx.go b/client/nginx.go index 11cf093a..c69f3770 100644 --- a/client/nginx.go +++ b/client/nginx.go @@ -494,6 +494,60 @@ type HTTPLimitConnections map[string]LimitConnection // StreamLimitConnections represents limit connections related stats type StreamLimitConnections map[string]LimitConnection +type option func(*NginxClient) error + +// WithHTTPClient configures NGINX Plus Client to use customized http.Client. +// +//nolint:all +func WithHTTPClient(httpClient *http.Client) option { + return func(nc *NginxClient) error { + if httpClient == nil { + return errors.New("nil http client") + } + nc.httpClient = httpClient + return nil + } +} + +// WithAPIVersion configures NGINX Plus Client to use a specific version of the NGINX Plus API. +// +//nolint:all +func WithAPIVersion(version int) option { + return func(nc *NginxClient) error { + if !versionSupported(version) { + return fmt.Errorf("unsupported API version %v", version) + } + nc.version = version + return nil + } +} + +// NewDefaultNginxClient creates the client for the latest NGINX plus version +// and configured default HTTP timeout set to 10 seconds. User can configure +// customized http.Client and supported API version by passing options. +// +// NewDefaultNginxClient does not perform external http request to determine +// what NGINX version it talks to. It is responsibility of the caller to make +// sure the library is used with supported versions. +func NewDefaultNginxClient(apiEndpoint string, opts ...option) (*NginxClient, error) { + if apiEndpoint == "" { + return nil, errors.New("api endpoint not specified") + } + nc := NginxClient{ + apiEndpoint: apiEndpoint, + httpClient: &http.Client{ + Timeout: 10 * time.Second, + }, + version: APIVersion, + } + for _, opt := range opts { + if err := opt(&nc); err != nil { + return nil, err + } + } + return &nc, nil +} + // NewNginxClient creates an NginxClient with the latest supported version. func NewNginxClient(httpClient *http.Client, apiEndpoint string) (*NginxClient, error) { return NewNginxClientWithVersion(httpClient, apiEndpoint, APIVersion) diff --git a/client/nginx_test.go b/client/nginx_test.go index b8aa4eb8..ba0f699a 100644 --- a/client/nginx_test.go +++ b/client/nginx_test.go @@ -1,8 +1,12 @@ package client import ( + "net/http" "reflect" "testing" + "time" + + "github.com/google/go-cmp/cmp" ) func TestDetermineUpdates(t *testing.T) { @@ -518,3 +522,61 @@ func TestHaveSameParametersForStream(t *testing.T) { } } } + +func TestClientFailsToCreateOnInvalidAPIVersionInput(t *testing.T) { + t.Parallel() + + invalidAPIVersions := []int{-1, 0, 3, 9} + for _, v := range invalidAPIVersions { + _, err := NewDefaultNginxClient("test-endpoint", WithAPIVersion(v)) + if err == nil { + t.Errorf("want error on using invalid API version %d, got nil", v) + } + } +} + +func TestClientFailsToCreateOnInvalidInputForHTTPClient(t *testing.T) { + t.Parallel() + + _, err := NewDefaultNginxClient("test-endpoint", WithHTTPClient(nil)) + if err == nil { + t.Error("want error on using nil http client, got nil") + } +} + +func TestClientFailsToCreateOnInvalidAPIEndpointInput(t *testing.T) { + t.Parallel() + + _, err := NewDefaultNginxClient("") + if err == nil { + t.Error("want error on empty endpoint, got nil") + } +} + +func TestNewCreatesDefaultClientOnValidCustomHTTPClientInput(t *testing.T) { + t.Parallel() + ht := &http.Client{ + Timeout: 30 * time.Second, + } + + client, err := NewDefaultNginxClient("test-enpoint", WithHTTPClient(ht)) + if err != nil { + t.Fatal(err) + } + if !cmp.Equal(ht, client.httpClient) { + t.Errorf(cmp.Diff(ht, client.httpClient)) + } +} + +func TestNewCreatesDefaultClientOnValidAPIVersionInput(t *testing.T) { + t.Parallel() + + apiVer := 7 + client, err := NewDefaultNginxClient("test-endpoint", WithAPIVersion(apiVer)) + if err != nil { + t.Fatal(err) + } + if !cmp.Equal(apiVer, client.version) { + t.Error(cmp.Diff(apiVer, client.version)) + } +} diff --git a/examples/default-client/main.go b/examples/default-client/main.go new file mode 100644 index 00000000..8699ab46 --- /dev/null +++ b/examples/default-client/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "time" + + "github.com/nginxinc/nginx-plus-go-client/client" +) + +func main() { + // Create a custom HTTP Client + myHTTPClient := &http.Client{ + Timeout: 60 * time.Second, + } + + // Create NGINX Plus Client for working with version 8 + c, err := client.NewDefaultNginxClient( + "https://demo.nginx.com/api", + client.WithHTTPClient(myHTTPClient), + client.WithAPIVersion(8), + ) + if err != nil { + // handle error + log.Fatal(err) + } + + // Retrieve info about running NGINX + info, err := c.GetNginxInfo() + if err != nil { + // handle error + log.Fatal(err) + } + fmt.Printf("%+v\n", info) + + // Prints + // &{Version:1.21.6 Build:nginx-plus-r27 Address:3.125.64.247 Generation:4 LoadTimestamp:2022-09-14T12:45:25.218Z Timestamp:2022-10-10T12:25:16.552Z ProcessID:3640888 ParentProcessID:2945895} + +} diff --git a/go.mod b/go.mod index 8916c37c..da44e422 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/nginxinc/nginx-plus-go-client go 1.19 + +require github.com/google/go-cmp v0.5.8 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..e9b099ce --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= From 87d2b4c6018e8f359849a99e115f16336c9f4e35 Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Tue, 11 Oct 2022 14:31:21 +0100 Subject: [PATCH 2/2] Update README doc --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 394e2e93..b334d9e5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,12 @@ import "github.com/nginxinc/nginx-plus-go-client/client" ## Creating a client -Create a new ```Client``` that works with the latest NGINX API Version: +When creating a new ```client``` it is assumed that NGINX API is reachable. The ```client``` verifies API version of the running NGINX at the time of creating a new client object. + +If user wants to create a client without NGINX automatic version check it should use the ```NewDefaultNginxClient``` func to create the client. + + +### Create a new ```Client``` that works with the latest NGINX API Version: ```go myHTTPClient := &http.Client{ Timeout: 5*time.Second @@ -41,7 +46,7 @@ if err != nil { } ``` -Create a new ```Client``` with specified API version: +### Create a new ```Client``` with specified API version: ```go myHTTPClient := &http.Client{ Timeout: 5*time.Second @@ -53,16 +58,14 @@ if err != nil { } ``` - - -Create a new default ```Client```: +### Create a new default ```Client``` that does not check NGINX API version: ```go c, err := client.NewDefaultNginxClient("your-api-endpoint") if err != nil { // handle error } ``` -Create a new default client with customized http.Client and API Version: +### Create a new default client with customized ```http.Client``` and API Version: ```go myHTTPClient := &http.Client{ Timeout: 60 * time.Second, @@ -79,7 +82,7 @@ if err != nil { ``` Note that: - default NGINX Plus Client is using ```http.Client``` with specified ```Timeout``` value of 10s -- it is user's responsibility to provide correct NGINX API version +- it is user's responsibility to provide correct NGINX API version as the ```NewDefaultNginxClient``` func does not verify the version ## Testing