From 2c926f9db8741437cd244cbac8eace0e13956b5e Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Tue, 22 Nov 2022 18:04:41 +0000
Subject: [PATCH 01/21] added config for tls in API server
---
sdk/config_helpers_test.go | 3 +-
src/core/config/config.go | 12 ++--
src/core/config/defaults.go | 12 ++++
src/core/config/types.go | 2 +
src/core/nginx.go | 4 +-
src/plugins/agent_api.go | 22 +++++--
.../nginx/agent/v2/src/core/config/config.go | 12 ++--
.../agent/v2/src/core/config/defaults.go | 12 ++++
.../nginx/agent/v2/src/core/config/types.go | 2 +
.../core/metrics/sources/nginx_access_log.go | 57 +++++++++++++++----
.../nginx/agent/v2/src/core/nginx.go | 4 +-
.../monitoring/processor/nap.go | 6 +-
.../nginx/agent/v2/src/plugins/agent_api.go | 22 +++++--
.../agent/v2/src/plugins/nap_monitoring.go | 2 +-
14 files changed, 132 insertions(+), 40 deletions(-)
diff --git a/sdk/config_helpers_test.go b/sdk/config_helpers_test.go
index acbddccac..3127a8c90 100644
--- a/sdk/config_helpers_test.go
+++ b/sdk/config_helpers_test.go
@@ -1327,8 +1327,7 @@ func TestAddAuxfileToNginxConfig(t *testing.T) {
assert.NoError(t, err)
if test.expected.Zaux != nil {
- assert.Equal(t, test.expected.Zaux.Checksum, nginxConfig.Zaux.Checksum)
- assert.Equal(t, test.expected.Zaux.Checksum, nginxConfig.Zaux.Checksum)
+ assert.Equal(t, test.expected.Zaux.Checksum, nginxConfig.GetZaux().GetChecksum())
zf, err := zip.NewReader(nginxConfig.Zaux)
assert.NoError(t, err)
files := make(map[string]struct{})
diff --git a/src/core/config/config.go b/src/core/config/config.go
index 63e74ce4d..7606817dd 100644
--- a/src/core/config/config.go
+++ b/src/core/config/config.go
@@ -337,6 +337,8 @@ func getServer() Server {
func getAgentAPI() AgentAPI {
return AgentAPI{
Port: Viper.GetInt(AgentAPIPort),
+ Cert: Viper.GetString(AgentAPICert),
+ Key: Viper.GetString(AgentAPIKey),
}
}
@@ -355,7 +357,7 @@ func LoadPropertiesFromFile(cfg string) error {
Viper.SetConfigType(ConfigFileType)
err := Viper.MergeInConfig()
if err != nil {
- return fmt.Errorf("Error loading config file %s: %v", cfg, err)
+ return fmt.Errorf("error loading config file %s: %v", cfg, err)
}
// Get the dynamic config path and use default dynamic config path if it's not
@@ -376,12 +378,12 @@ func LoadPropertiesFromFile(cfg string) error {
log.Infof("Writing the following file to disk: %s", dynamicCfgPath)
err = os.MkdirAll(dynamicCfgDir, 0755)
if err != nil {
- return fmt.Errorf("Error attempting to create directory for dynamic config (%s), got the following error: %v", dynamicCfgDir, err)
+ return fmt.Errorf("error attempting to create directory for dynamic config (%s), got the following error: %v", dynamicCfgDir, err)
}
err = os.WriteFile(dynamicCfgPath, []byte(dynamicConfigUsageComment), 0644)
if err != nil {
- return fmt.Errorf("Error attempting to create dynamic config (%s), got the following error: %v", dynamicCfgPath, err)
+ return fmt.Errorf("error attempting to create dynamic config (%s), got the following error: %v", dynamicCfgPath, err)
}
}
@@ -391,7 +393,7 @@ func LoadPropertiesFromFile(cfg string) error {
Viper.SetConfigName(dynamicCfgFile)
err = Viper.MergeInConfig()
if err != nil {
- return fmt.Errorf("Error loading file %s: %v", dynamicCfgPath, err)
+ return fmt.Errorf("error loading file %s: %v", dynamicCfgPath, err)
}
return nil
@@ -418,7 +420,7 @@ func SeekConfigFileInPaths(configName string, searchPaths ...string) (string, er
return f, nil
}
}
- return "", fmt.Errorf("A valid configuration has not been found in any of the search paths.")
+ return "", fmt.Errorf("a valid configuration has not been found in any of the search paths.")
}
func filePathUTime(path string) time.Time {
diff --git a/src/core/config/defaults.go b/src/core/config/defaults.go
index 17c088c70..5429efe18 100644
--- a/src/core/config/defaults.go
+++ b/src/core/config/defaults.go
@@ -117,6 +117,8 @@ const (
APIKey = "api"
AgentAPIPort = APIKey + agent_config.KeyDelimiter + "port"
+ AgentAPICert = APIKey + agent_config.KeyDelimiter + "cert"
+ AgentAPIKey = APIKey + agent_config.KeyDelimiter + "key"
// viper keys used in config
TlsKey = "tls"
@@ -232,6 +234,16 @@ var (
Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.",
DefaultValue: Defaults.AgentAPI.Port,
},
+ &StringFlag{
+ Name: AgentAPICert,
+ Usage: "The cert used by the Agent API.",
+ DefaultValue: "",
+ },
+ &StringFlag{
+ Name: AgentAPIKey,
+ Usage: "The key used by the Agent API.",
+ DefaultValue: "",
+ },
&StringFlag{
Name: ConfigDirsKey,
Usage: "Defines the paths that you want to grant nginx-agent read/write access to. This key is formatted as a string and follows Unix PATH format.",
diff --git a/src/core/config/types.go b/src/core/config/types.go
index 976cee3a6..f47145ae5 100644
--- a/src/core/config/types.go
+++ b/src/core/config/types.go
@@ -43,6 +43,8 @@ type Server struct {
type AgentAPI struct {
Port int `mapstructure:"port" yaml:"-"`
+ Cert string `mapstructure:"cert" yaml:"-"`
+ Key string `mapstructure:"key" yaml:"-"`
}
// LogConfig for logging
diff --git a/src/core/nginx.go b/src/core/nginx.go
index 164a89302..bd16b7273 100644
--- a/src/core/nginx.go
+++ b/src/core/nginx.go
@@ -341,7 +341,7 @@ func (n *NginxBinaryType) WriteConfig(config *proto.NginxConfig) (*sdk.ConfigApp
}
// Ensure all aux files are within the allowed list directories.
- if err := ensureFilesAllowed(auxFiles, n.config.AllowedDirectoriesMap, config.Zaux.RootDirectory); err != nil {
+ if err := ensureFilesAllowed(auxFiles, n.config.AllowedDirectoriesMap, config.GetZaux().GetRootDirectory()); err != nil {
return nil, err
}
@@ -365,7 +365,7 @@ func (n *NginxBinaryType) WriteConfig(config *proto.NginxConfig) (*sdk.ConfigApp
}
if len(auxFiles) > 0 {
- auxPath := config.Zaux.RootDirectory
+ auxPath := config.GetZaux().GetRootDirectory()
err = n.env.WriteFiles(configApply, auxFiles, auxPath, n.config.AllowedDirectoriesMap)
if err != nil {
log.Warnf("Auxiliary files write failed: %s", err)
diff --git a/src/plugins/agent_api.go b/src/plugins/agent_api.go
index 5bb369be2..d04b8468a 100644
--- a/src/plugins/agent_api.go
+++ b/src/plugins/agent_api.go
@@ -28,6 +28,12 @@ type NginxHandler struct {
nginxBinary core.NginxBinary
}
+const (
+ contentTypeHeader = "Content-Type"
+ jsonMimeType = "application/json"
+)
+
+
func NewAgentAPI(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *AgentAPI {
return &AgentAPI{config: config, env: env, nginxBinary: nginxBinary}
}
@@ -61,15 +67,21 @@ func (a *AgentAPI) createHttpServer() {
a.nginxHandler = &NginxHandler{a.env, a.nginxBinary}
mux.Handle("/nginx/", a.nginxHandler)
- log.Debug("Starting Agent API HTTP server")
-
a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
}
- if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
- log.Fatalf("error listening to port: %v", err)
+ if a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" {
+ log.Debug("Starting Agent API HTTP server with cert and key")
+ if err := a.server.ListenAndServeTLS(a.config.AgentAPI.Cert, a.config.AgentAPI.Key); err != http.ErrServerClosed {
+ log.Fatalf("error listening to port: %v", err)
+ }
+ } else {
+ log.Debug("Starting Agent API HTTP server with cert and key")
+ if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
+ log.Fatalf("error listening to port: %v", err)
+ }
}
}
@@ -78,7 +90,7 @@ var (
)
func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("content-type", "application/json")
+ w.Header().Set(contentTypeHeader, jsonMimeType)
switch {
case r.Method == http.MethodGet && instancesRegex.MatchString(r.URL.Path):
err := sendInstanceDetailsPayload(h.getNginxDetails(), w, r)
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go
index 63e74ce4d..7606817dd 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go
@@ -337,6 +337,8 @@ func getServer() Server {
func getAgentAPI() AgentAPI {
return AgentAPI{
Port: Viper.GetInt(AgentAPIPort),
+ Cert: Viper.GetString(AgentAPICert),
+ Key: Viper.GetString(AgentAPIKey),
}
}
@@ -355,7 +357,7 @@ func LoadPropertiesFromFile(cfg string) error {
Viper.SetConfigType(ConfigFileType)
err := Viper.MergeInConfig()
if err != nil {
- return fmt.Errorf("Error loading config file %s: %v", cfg, err)
+ return fmt.Errorf("error loading config file %s: %v", cfg, err)
}
// Get the dynamic config path and use default dynamic config path if it's not
@@ -376,12 +378,12 @@ func LoadPropertiesFromFile(cfg string) error {
log.Infof("Writing the following file to disk: %s", dynamicCfgPath)
err = os.MkdirAll(dynamicCfgDir, 0755)
if err != nil {
- return fmt.Errorf("Error attempting to create directory for dynamic config (%s), got the following error: %v", dynamicCfgDir, err)
+ return fmt.Errorf("error attempting to create directory for dynamic config (%s), got the following error: %v", dynamicCfgDir, err)
}
err = os.WriteFile(dynamicCfgPath, []byte(dynamicConfigUsageComment), 0644)
if err != nil {
- return fmt.Errorf("Error attempting to create dynamic config (%s), got the following error: %v", dynamicCfgPath, err)
+ return fmt.Errorf("error attempting to create dynamic config (%s), got the following error: %v", dynamicCfgPath, err)
}
}
@@ -391,7 +393,7 @@ func LoadPropertiesFromFile(cfg string) error {
Viper.SetConfigName(dynamicCfgFile)
err = Viper.MergeInConfig()
if err != nil {
- return fmt.Errorf("Error loading file %s: %v", dynamicCfgPath, err)
+ return fmt.Errorf("error loading file %s: %v", dynamicCfgPath, err)
}
return nil
@@ -418,7 +420,7 @@ func SeekConfigFileInPaths(configName string, searchPaths ...string) (string, er
return f, nil
}
}
- return "", fmt.Errorf("A valid configuration has not been found in any of the search paths.")
+ return "", fmt.Errorf("a valid configuration has not been found in any of the search paths.")
}
func filePathUTime(path string) time.Time {
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
index 17c088c70..5429efe18 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
@@ -117,6 +117,8 @@ const (
APIKey = "api"
AgentAPIPort = APIKey + agent_config.KeyDelimiter + "port"
+ AgentAPICert = APIKey + agent_config.KeyDelimiter + "cert"
+ AgentAPIKey = APIKey + agent_config.KeyDelimiter + "key"
// viper keys used in config
TlsKey = "tls"
@@ -232,6 +234,16 @@ var (
Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.",
DefaultValue: Defaults.AgentAPI.Port,
},
+ &StringFlag{
+ Name: AgentAPICert,
+ Usage: "The cert used by the Agent API.",
+ DefaultValue: "",
+ },
+ &StringFlag{
+ Name: AgentAPIKey,
+ Usage: "The key used by the Agent API.",
+ DefaultValue: "",
+ },
&StringFlag{
Name: ConfigDirsKey,
Usage: "Defines the paths that you want to grant nginx-agent read/write access to. This key is formatted as a string and follows Unix PATH format.",
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go
index 976cee3a6..f47145ae5 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go
@@ -43,6 +43,8 @@ type Server struct {
type AgentAPI struct {
Port int `mapstructure:"port" yaml:"-"`
+ Cert string `mapstructure:"cert" yaml:"-"`
+ Key string `mapstructure:"key" yaml:"-"`
}
// LogConfig for logging
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go
index f18dc9fb7..69d0dcf43 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/metrics/sources/nginx_access_log.go
@@ -18,6 +18,10 @@ import (
"github.com/nginx/agent/v2/src/core/metrics/sources/tailer"
)
+const (
+ spaceDelim = " "
+)
+
// This metrics source is used to tail the NGINX access logs to retrieve http metrics.
type NginxAccessLog struct {
@@ -207,26 +211,30 @@ func (c *NginxAccessLog) logStats(ctx context.Context, logFile, logFormat string
}
if access.Request != "" {
- splitRequest := strings.Split(access.Request, " ")
- n := fmt.Sprintf("method.%s", strings.ToLower(splitRequest[0]))
+ method, _, protocol := getParsedRequest(access.Request)
+ n := fmt.Sprintf("method.%s", strings.ToLower(method))
if isOtherMethod(n) {
n = "method.others"
}
counters[n] = counters[n] + 1
if access.ServerProtocol == "" {
- httpProtocolVersion := strings.Split(splitRequest[2], "/")[1]
- httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_")
- n = fmt.Sprintf("v%s", httpProtocolVersion)
- counters[n] = counters[n] + 1
+ if strings.Count(protocol, "/") == 1 {
+ httpProtocolVersion := strings.Split(protocol, "/")[1]
+ httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_")
+ n = fmt.Sprintf("v%s", httpProtocolVersion)
+ counters[n] = counters[n] + 1
+ }
}
}
if access.ServerProtocol != "" {
- httpProtocolVersion := strings.Split(access.ServerProtocol, "/")[1]
- httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_")
- n := fmt.Sprintf("v%s", httpProtocolVersion)
- counters[n] = counters[n] + 1
+ if strings.Count(access.ServerProtocol, "/") == 1 {
+ httpProtocolVersion := strings.Split(access.ServerProtocol, "/")[1]
+ httpProtocolVersion = strings.ReplaceAll(httpProtocolVersion, ".", "_")
+ n := fmt.Sprintf("v%s", httpProtocolVersion)
+ counters[n] = counters[n] + 1
+ }
}
// don't need the http status for NGINX Plus
@@ -294,6 +302,35 @@ func (c *NginxAccessLog) logStats(ctx context.Context, logFile, logFormat string
}
}
+func getParsedRequest(request string) (method string, uri string, protocol string) {
+ if len(request) == 0 {
+ return
+ }
+
+ startURIIdx := strings.Index(request, spaceDelim)
+ if startURIIdx == -1 {
+ return
+ }
+
+ endURIIdx := strings.LastIndex(request, spaceDelim)
+ // Ideally, endURIIdx should never be -1 here, as startURIIdx should have handled it already
+ if endURIIdx == -1 {
+ return
+ }
+
+ // For Example: GET /user/register?ahrefp' or ' HTTP/1.1
+
+ // method -> GET
+ method = request[:startURIIdx]
+
+ // uri -> /user/register?ahrefp' or '
+ uri = request[startURIIdx+1 : endURIIdx]
+
+ // protocol -> HTTP/1.1
+ protocol = request[endURIIdx+1:]
+ return
+}
+
func getRequestLengthMetricValue(requestLengths []float64) float64 {
value := 0.0
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/nginx.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/nginx.go
index 164a89302..bd16b7273 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/nginx.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/nginx.go
@@ -341,7 +341,7 @@ func (n *NginxBinaryType) WriteConfig(config *proto.NginxConfig) (*sdk.ConfigApp
}
// Ensure all aux files are within the allowed list directories.
- if err := ensureFilesAllowed(auxFiles, n.config.AllowedDirectoriesMap, config.Zaux.RootDirectory); err != nil {
+ if err := ensureFilesAllowed(auxFiles, n.config.AllowedDirectoriesMap, config.GetZaux().GetRootDirectory()); err != nil {
return nil, err
}
@@ -365,7 +365,7 @@ func (n *NginxBinaryType) WriteConfig(config *proto.NginxConfig) (*sdk.ConfigApp
}
if len(auxFiles) > 0 {
- auxPath := config.Zaux.RootDirectory
+ auxPath := config.GetZaux().GetRootDirectory()
err = n.env.WriteFiles(configApply, auxFiles, auxPath, n.config.AllowedDirectoriesMap)
if err != nil {
log.Warnf("Auxiliary files write failed: %s", err)
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go b/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go
index ea34ce712..9a93efe49 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/extensions/nginx-app-protect/monitoring/processor/nap.go
@@ -81,7 +81,6 @@ var (
subViolations,
supportID,
threatCampaignNames,
- httpURI,
violationRating,
httpHostname,
xForwardedForHeaderVal,
@@ -97,6 +96,7 @@ var (
clientApplication,
clientApplicationVersion,
transportProtocol,
+ httpURI,
}
)
@@ -464,7 +464,7 @@ func setValue(napConfig *NAPConfig, key, value string, logger *logrus.Entry) err
case sigSetNames:
napConfig.SigSetNames = replaceEncodedList(value, listSeperator)
case threatCampaignNames:
- napConfig.ThreatCampaignNames = value
+ napConfig.ThreatCampaignNames = replaceEncodedList(value, listSeperator)
case violationDetails:
napConfig.ViolationDetailsXML = func(data string) *BADMSG {
var xmlData BADMSG
@@ -513,7 +513,7 @@ func setValue(napConfig *NAPConfig, key, value string, logger *logrus.Entry) err
case sigCVEs:
napConfig.SignatureCVEs = replaceEncodedList(value, listSeperator)
case subViolations:
- napConfig.SubViolations = value
+ napConfig.SubViolations = replaceEncodedList(value, listSeperator)
case supportID:
napConfig.SupportID = value
case violations:
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
index 5bb369be2..d04b8468a 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
@@ -28,6 +28,12 @@ type NginxHandler struct {
nginxBinary core.NginxBinary
}
+const (
+ contentTypeHeader = "Content-Type"
+ jsonMimeType = "application/json"
+)
+
+
func NewAgentAPI(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *AgentAPI {
return &AgentAPI{config: config, env: env, nginxBinary: nginxBinary}
}
@@ -61,15 +67,21 @@ func (a *AgentAPI) createHttpServer() {
a.nginxHandler = &NginxHandler{a.env, a.nginxBinary}
mux.Handle("/nginx/", a.nginxHandler)
- log.Debug("Starting Agent API HTTP server")
-
a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
}
- if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
- log.Fatalf("error listening to port: %v", err)
+ if a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" {
+ log.Debug("Starting Agent API HTTP server with cert and key")
+ if err := a.server.ListenAndServeTLS(a.config.AgentAPI.Cert, a.config.AgentAPI.Key); err != http.ErrServerClosed {
+ log.Fatalf("error listening to port: %v", err)
+ }
+ } else {
+ log.Debug("Starting Agent API HTTP server with cert and key")
+ if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
+ log.Fatalf("error listening to port: %v", err)
+ }
}
}
@@ -78,7 +90,7 @@ var (
)
func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("content-type", "application/json")
+ w.Header().Set(contentTypeHeader, jsonMimeType)
switch {
case r.Method == http.MethodGet && instancesRegex.MatchString(r.URL.Path):
err := sendInstanceDetailsPayload(h.getNginxDetails(), w, r)
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go
index 832b88562..e10d28b1b 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/nap_monitoring.go
@@ -108,7 +108,7 @@ func (n *NAPMonitoring) run() {
}
case <-riTicker.C:
if len(report.Events) > 0 {
- log.Infof("reached a report interval of %vs, sending %d Security Violation Events as a report", n.reportInterval.Seconds(), n.reportCount)
+ log.Infof("reached a report interval of %vs, sending %d Security Violation Events as a report", n.reportInterval.Seconds(), len(report.Events))
n.send(report)
}
case <-n.ctx.Done():
From 81bfc0cb38347a0e33f1a5495925b7ae89c612ad Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Wed, 23 Nov 2022 17:55:12 +0000
Subject: [PATCH 02/21] wip: need more tests
---
Makefile | 22 +-
go.mod | 5 +-
go.sum | 3 +
nginx-agent.conf | 2 +
src/core/metrics/collectors/nginx_test.go | 2 +-
src/plugins/agent_api.go | 17 +-
src/plugins/agent_api_test.go | 66 +
src/plugins/events_test.go | 38 +-
test/performance/go.sum | 1 +
.../nginx/agent/v2/src/plugins/agent_api.go | 17 +-
.../github.com/go-resty/resty/v2/.gitignore | 30 +
.../github.com/go-resty/resty/v2/BUILD.bazel | 48 +
vendor/github.com/go-resty/resty/v2/LICENSE | 21 +
vendor/github.com/go-resty/resty/v2/README.md | 906 ++
vendor/github.com/go-resty/resty/v2/WORKSPACE | 31 +
vendor/github.com/go-resty/resty/v2/client.go | 1115 ++
.../go-resty/resty/v2/middleware.go | 543 +
.../github.com/go-resty/resty/v2/redirect.go | 101 +
.../github.com/go-resty/resty/v2/request.go | 896 ++
.../github.com/go-resty/resty/v2/response.go | 175 +
vendor/github.com/go-resty/resty/v2/resty.go | 40 +
vendor/github.com/go-resty/resty/v2/retry.go | 221 +
vendor/github.com/go-resty/resty/v2/trace.go | 130 +
.../github.com/go-resty/resty/v2/transport.go | 35 +
.../go-resty/resty/v2/transport112.go | 34 +
vendor/github.com/go-resty/resty/v2/util.go | 391 +
vendor/golang.org/x/net/publicsuffix/list.go | 182 +
vendor/golang.org/x/net/publicsuffix/table.go | 10585 ++++++++++++++++
vendor/modules.txt | 4 +
29 files changed, 15605 insertions(+), 56 deletions(-)
create mode 100644 vendor/github.com/go-resty/resty/v2/.gitignore
create mode 100644 vendor/github.com/go-resty/resty/v2/BUILD.bazel
create mode 100644 vendor/github.com/go-resty/resty/v2/LICENSE
create mode 100644 vendor/github.com/go-resty/resty/v2/README.md
create mode 100644 vendor/github.com/go-resty/resty/v2/WORKSPACE
create mode 100644 vendor/github.com/go-resty/resty/v2/client.go
create mode 100644 vendor/github.com/go-resty/resty/v2/middleware.go
create mode 100644 vendor/github.com/go-resty/resty/v2/redirect.go
create mode 100644 vendor/github.com/go-resty/resty/v2/request.go
create mode 100644 vendor/github.com/go-resty/resty/v2/response.go
create mode 100644 vendor/github.com/go-resty/resty/v2/resty.go
create mode 100644 vendor/github.com/go-resty/resty/v2/retry.go
create mode 100644 vendor/github.com/go-resty/resty/v2/trace.go
create mode 100644 vendor/github.com/go-resty/resty/v2/transport.go
create mode 100644 vendor/github.com/go-resty/resty/v2/transport112.go
create mode 100644 vendor/github.com/go-resty/resty/v2/util.go
create mode 100644 vendor/golang.org/x/net/publicsuffix/list.go
create mode 100644 vendor/golang.org/x/net/publicsuffix/table.go
diff --git a/Makefile b/Makefile
index dd9c5851c..35a971aa8 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,14 @@ TEST_BUILD_DIR := build/test
# override this value if you want to change the architecture. GOOS options here: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63
LOCAL_ARCH := amd64
+CERT_CLIENT_CA_CN := client-ca.local
+CERT_CLIENT_INT_CN := client-int.local
+CERT_CLIENT_EE_CN := client-ee.local
+CERT_CLIENT_DNS := client.local
+CERT_SERVER_CA_CN := server-ca.local
+CERT_SERVER_INT_CN := server-int.local
+CERT_SERVER_EE_CN := server-ee.local
+CERT_SERVER_DNS := tls.example.com
$(TEST_BUILD_DIR):
mkdir -p $(TEST_BUILD_DIR)
@@ -164,27 +172,29 @@ test-install: ## Run agent install test
# Cert Generation #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
certs: ## Generate TLS certificates
- scripts/mtls/gen_cnf.sh ca --cn 'client-ca.local' --state Cork --locality Cork --org NGINX --country IE --out ${CERTS_DIR}/client/conf
+ scripts/mtls/gen_cnf.sh ca --cn '${CERT_CLIENT_CA_CN}' --state Cork --locality Cork --org NGINX --country IE --out ${CERTS_DIR}/client/conf
scripts/mtls/gen_cert.sh ca --config ${CERTS_DIR}/client/conf/ca.cnf --out ${CERTS_DIR}/client
- scripts/mtls/gen_cnf.sh intermediate --cn 'client-int.local' --org NGINX --locality Cork --out ${CERTS_DIR}/client/conf
+ scripts/mtls/gen_cnf.sh intermediate --cn '${CERT_CLIENT_INT_CN}' --org NGINX --locality Cork --out ${CERTS_DIR}/client/conf
scripts/mtls/gen_cert.sh intermediate --config ${CERTS_DIR}/client/conf/int.cnf --ca-cert ${CERTS_DIR}/client/ca.crt --ca-key ${CERTS_DIR}/client/ca.key --out ${CERTS_DIR}/client
- scripts/mtls/gen_cnf.sh end-entity --cn 'client.local' --san 'DNS.1=client.local' --out ${CERTS_DIR}/client/conf
+ scripts/mtls/gen_cnf.sh end-entity --cn '${CERT_CLIENT_EE_CN}' --san 'DNS.1=${CERT_CLIENT_DNS}' --out ${CERTS_DIR}/client/conf
scripts/mtls/gen_cert.sh end-entity --config ${CERTS_DIR}/client/conf/ee.cnf --ca-cert ${CERTS_DIR}/client/int.crt --ca-key ${CERTS_DIR}/client/int.key --out ${CERTS_DIR}/client
cp ${CERTS_DIR}/client/ee.crt ${CERTS_DIR}/client.crt
cp ${CERTS_DIR}/client/ee.key ${CERTS_DIR}/client.key
- scripts/mtls/gen_cnf.sh ca --cn 'server-ca.local' --state Cork --locality Cork --org NGINX --country IE --out ${CERTS_DIR}/server/conf
+ scripts/mtls/gen_cnf.sh ca --cn '${CERT_SERVER_CA_CN}' --state Cork --locality Cork --org NGINX --country IE --out ${CERTS_DIR}/server/conf
scripts/mtls/gen_cert.sh ca --config ${CERTS_DIR}/server/conf/ca.cnf --out ${CERTS_DIR}/server
- scripts/mtls/gen_cnf.sh intermediate --cn 'server-int.local' --org NGINX --locality Cork --out ${CERTS_DIR}/server/conf
+ scripts/mtls/gen_cnf.sh intermediate --cn '${CERT_SERVER_INT_CN}' --org NGINX --locality Cork --out ${CERTS_DIR}/server/conf
scripts/mtls/gen_cert.sh intermediate --config ${CERTS_DIR}/server/conf/int.cnf --ca-cert ${CERTS_DIR}/server/ca.crt --ca-key ${CERTS_DIR}/server/ca.key --out ${CERTS_DIR}/server
- scripts/mtls/gen_cnf.sh end-entity --cn 'tls.example.com' --san 'DNS.1=tls.example.com' --out ${CERTS_DIR}/server/conf
+ scripts/mtls/gen_cnf.sh end-entity --cn '${CERT_SERVER_EE_CN}' --san 'DNS.1=${CERT_SERVER_DNS}' --out ${CERTS_DIR}/server/conf
scripts/mtls/gen_cert.sh end-entity --config ${CERTS_DIR}/server/conf/ee.cnf --ca-cert ${CERTS_DIR}/server/int.crt --ca-key ${CERTS_DIR}/server/int.key --out ${CERTS_DIR}/server
+ cat ${CERTS_DIR}/server/int.crt ${CERTS_DIR}/server/ca.crt > ${CERTS_DIR}/ca.pem
+
cp ${CERTS_DIR}/server/ee.crt ${CERTS_DIR}/server.crt
cp ${CERTS_DIR}/server/ee.key ${CERTS_DIR}/server.key
diff --git a/go.mod b/go.mod
index 0d76370c2..6b1e2e183 100644
--- a/go.mod
+++ b/go.mod
@@ -37,7 +37,10 @@ require (
gopkg.in/yaml.v3 v3.0.1
)
-require github.com/nginx/agent/sdk/v2 v2.0.0-00010101000000-000000000000
+require (
+ github.com/go-resty/resty/v2 v2.7.0
+ github.com/nginx/agent/sdk/v2 v2.0.0-00010101000000-000000000000
+)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
diff --git a/go.sum b/go.sum
index d166edcbf..9ad741b70 100644
--- a/go.sum
+++ b/go.sum
@@ -80,6 +80,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
+github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
@@ -381,6 +383,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 h1:N9Vc/rorQUDes6B9CNdIxAn5jODGj2wzfrei2x4wNj4=
golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
diff --git a/nginx-agent.conf b/nginx-agent.conf
index ebd24b4e9..b2b2bd26a 100644
--- a/nginx-agent.conf
+++ b/nginx-agent.conf
@@ -18,6 +18,8 @@ server:
api:
# port to expose http api
port: 9090
+ cert: "ca.pem"
+ key: "server.key"
# tls options
tls:
# enable tls in the nginx-agent setup for grpcs
diff --git a/src/core/metrics/collectors/nginx_test.go b/src/core/metrics/collectors/nginx_test.go
index 7dbc265d7..c88fa7e9f 100644
--- a/src/core/metrics/collectors/nginx_test.go
+++ b/src/core/metrics/collectors/nginx_test.go
@@ -184,7 +184,7 @@ func TestNginxCollector_UpdateConfig(t *testing.T) {
mockNginxSource2,
},
collectorConf: &metrics.NginxCollectorConfig{
- NginxId: "123",
+ NginxId: "124",
},
env: env,
}
diff --git a/src/plugins/agent_api.go b/src/plugins/agent_api.go
index d04b8468a..8fa383dab 100644
--- a/src/plugins/agent_api.go
+++ b/src/plugins/agent_api.go
@@ -33,6 +33,9 @@ const (
jsonMimeType = "application/json"
)
+var (
+ instancesRegex = regexp.MustCompile(`^\/nginx[\/]*$`)
+)
func NewAgentAPI(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *AgentAPI {
return &AgentAPI{config: config, env: env, nginxBinary: nginxBinary}
@@ -72,23 +75,21 @@ func (a *AgentAPI) createHttpServer() {
Handler: mux,
}
- if a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" {
- log.Debug("Starting Agent API HTTP server with cert and key")
+ if (a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" && a.config.AgentAPI.Port != 0) {
+ log.Info("Starting Agent API HTTP server with cert and key and port from config")
if err := a.server.ListenAndServeTLS(a.config.AgentAPI.Cert, a.config.AgentAPI.Key); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
}
- } else {
- log.Debug("Starting Agent API HTTP server with cert and key")
+ } else if (a.config.AgentAPI.Port != 0) {
+ log.Info("Starting Agent API HTTP server with port from config and TLS disabled")
if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
}
+ } else {
+ log.Info("Agent API not started")
}
}
-var (
- instancesRegex = regexp.MustCompile(`^\/nginx[\/]*$`)
-)
-
func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set(contentTypeHeader, jsonMimeType)
switch {
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index 2b243167f..a17742688 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -1,13 +1,20 @@
package plugins
import (
+ "context"
"encoding/json"
+ "fmt"
"net/http"
"net/http/httptest"
+ // "os/exec"
"testing"
"github.com/nginx/agent/sdk/v2/proto"
+ "github.com/nginx/agent/v2/src/core"
+ "github.com/nginx/agent/v2/src/core/config"
+ tutils "github.com/nginx/agent/v2/test/utils"
"github.com/stretchr/testify/assert"
+ "github.com/go-resty/resty/v2"
)
func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) {
@@ -76,3 +83,62 @@ func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) {
})
}
}
+
+func TestMtlsForApi(t *testing.T) {
+ dir := t.TempDir()
+ t.Logf("%v", dir)
+ conf := &config.Config{
+ AgentAPI: config.AgentAPI{
+ Port: 2345,
+ },
+ }
+
+ // generateCertificate(t, dir)
+ pluginUnderTest := NewAgentAPI(conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
+ pluginUnderTest.Init(core.NewMockMessagePipe(context.TODO()))
+
+ client := resty.New()
+
+ resp, err := client.R().EnableTrace().Get(fmt.Sprintf("http://localhost:%d/nginx", conf.AgentAPI.Port))
+
+ // Explore response object
+ fmt.Println("Response Info:")
+ fmt.Println(" Error :", err)
+ fmt.Println(" Status Code:", resp.StatusCode())
+ fmt.Println(" Status :", resp.Status())
+ fmt.Println(" Proto :", resp.Proto())
+ fmt.Println(" Time :", resp.Time())
+ fmt.Println(" Received At:", resp.ReceivedAt())
+ fmt.Println(" Body :\n", resp)
+ fmt.Println()
+
+ var details []*proto.NginxDetails
+ json.Unmarshal(resp.Body(),&details)
+ // var responseItems map[string]*proto.NginxDetails
+ expected := tutils.GetDetailsMap()["12345"]
+ assert.Len(t, details, 1)
+ assert.Equal(t, expected, details[0])
+}
+
+// func generateCertificate(t *testing.T, dir string) error {
+// cmd := exec.Command("../../scripts/mtls/gen_cnf.sh", "ca", "--cn", "'ca.local'", "--state", "Cork", "--locality", "Cork", "--org", "NGINX", "--country", "IE", "--out", dir)
+
+// err := cmd.Run()
+// if err != nil {
+// t.Logf("%v", err)
+// t.Fail()
+// }
+
+// cmd1 := exec.Command("../../scripts/mtls/gen_cert.sh", "ca", "--config", "certs/conf/ca.cnf", "--out", dir)
+
+// err = cmd1.Run()
+// if err != nil {
+// t.Logf("%v", err)
+// t.Fail()
+// }
+
+// // scripts/mtls/gen_cnf.sh ca --cn '${CERT_CLIENT_CA_CN}' --state Cork --locality Cork --org NGINX --country IE --out ${CERTS_DIR}/client/conf
+// // scripts/mtls/gen_cert.sh ca --config ${CERTS_DIR}/client/conf/ca.cnf --out ${CERTS_DIR}/client
+
+// return nil
+// }
diff --git a/src/plugins/events_test.go b/src/plugins/events_test.go
index 70f6a003e..e127833b4 100644
--- a/src/plugins/events_test.go
+++ b/src/plugins/events_test.go
@@ -53,15 +53,7 @@ func TestActivityEvents_Process(t *testing.T) {
}{
{
name: "test NginxInstancesFound message",
- message: core.NewMessage(core.NginxInstancesFound, map[string]*proto.NginxDetails{
- "12345": {
- ProcessPath: "/path/to/nginx",
- NginxId: "12345",
- ProcessId: "123",
- Version: "1.2.1",
- StartTime: 1564894894,
- },
- }),
+ message: core.NewMessage(core.NginxInstancesFound, tutils.GetDetailsMap()),
msgTopics: []string{
core.NginxInstancesFound,
core.Events,
@@ -90,13 +82,7 @@ func TestActivityEvents_Process(t *testing.T) {
name: "test NginxReloadComplete message - reload failed",
message: core.NewMessage(core.NginxReloadComplete, NginxReloadResponse{
succeeded: false,
- nginxDetails: &proto.NginxDetails{
- ProcessPath: "/path/to/nginx",
- NginxId: "12345",
- ProcessId: "123",
- Version: "1.2.1",
- StartTime: 1564894894,
- },
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
@@ -126,13 +112,7 @@ func TestActivityEvents_Process(t *testing.T) {
name: "test NginxReloadComplete message - reload succeeded",
message: core.NewMessage(core.NginxReloadComplete, NginxReloadResponse{
succeeded: true,
- nginxDetails: &proto.NginxDetails{
- ProcessPath: "/path/to/nginx",
- NginxId: "12345",
- ProcessId: "124",
- Version: "1.2.1",
- StartTime: 1564894891,
- },
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
@@ -150,7 +130,7 @@ func TestActivityEvents_Process(t *testing.T) {
},
Data: &eventsProto.Event_ActivityEvent{
ActivityEvent: &eventsProto.ActivityEvent{
- Message: "nginx-v1.2.1 master process (pid: 124) reloaded successfully",
+ Message: "nginx-v1.2.1 master process (pid: 123) reloaded successfully",
Dimensions: expectedNginxDimensions,
},
},
@@ -253,10 +233,7 @@ func TestActivityEvents_Process(t *testing.T) {
name: "test successful ConfigRollbackResponse message",
message: core.NewMessage(core.ConfigRollbackResponse, ConfigRollbackResponse{
succeeded: true,
- nginxDetails: &proto.NginxDetails{
- ProcessPath: "/path/to/nginx",
- NginxId: "12345",
- },
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
@@ -286,10 +263,7 @@ func TestActivityEvents_Process(t *testing.T) {
name: "test failed ConfigRollbackResponse message",
message: core.NewMessage(core.ConfigRollbackResponse, ConfigRollbackResponse{
succeeded: false,
- nginxDetails: &proto.NginxDetails{
- ProcessPath: "/path/to/nginx",
- NginxId: "12345",
- },
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
diff --git a/test/performance/go.sum b/test/performance/go.sum
index 2bae3fadb..e04f9a4a1 100644
--- a/test/performance/go.sum
+++ b/test/performance/go.sum
@@ -96,6 +96,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
index d04b8468a..8fa383dab 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
@@ -33,6 +33,9 @@ const (
jsonMimeType = "application/json"
)
+var (
+ instancesRegex = regexp.MustCompile(`^\/nginx[\/]*$`)
+)
func NewAgentAPI(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *AgentAPI {
return &AgentAPI{config: config, env: env, nginxBinary: nginxBinary}
@@ -72,23 +75,21 @@ func (a *AgentAPI) createHttpServer() {
Handler: mux,
}
- if a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" {
- log.Debug("Starting Agent API HTTP server with cert and key")
+ if (a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" && a.config.AgentAPI.Port != 0) {
+ log.Info("Starting Agent API HTTP server with cert and key and port from config")
if err := a.server.ListenAndServeTLS(a.config.AgentAPI.Cert, a.config.AgentAPI.Key); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
}
- } else {
- log.Debug("Starting Agent API HTTP server with cert and key")
+ } else if (a.config.AgentAPI.Port != 0) {
+ log.Info("Starting Agent API HTTP server with port from config and TLS disabled")
if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
}
+ } else {
+ log.Info("Agent API not started")
}
}
-var (
- instancesRegex = regexp.MustCompile(`^\/nginx[\/]*$`)
-)
-
func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set(contentTypeHeader, jsonMimeType)
switch {
diff --git a/vendor/github.com/go-resty/resty/v2/.gitignore b/vendor/github.com/go-resty/resty/v2/.gitignore
new file mode 100644
index 000000000..9e856bd48
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/.gitignore
@@ -0,0 +1,30 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+coverage.out
+coverage.txt
+
+# Exclude intellij IDE folders
+.idea/*
diff --git a/vendor/github.com/go-resty/resty/v2/BUILD.bazel b/vendor/github.com/go-resty/resty/v2/BUILD.bazel
new file mode 100644
index 000000000..03bb44c3e
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/BUILD.bazel
@@ -0,0 +1,48 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@bazel_gazelle//:def.bzl", "gazelle")
+
+# gazelle:prefix github.com/go-resty/resty/v2
+# gazelle:go_naming_convention import_alias
+gazelle(name = "gazelle")
+
+go_library(
+ name = "resty",
+ srcs = [
+ "client.go",
+ "middleware.go",
+ "redirect.go",
+ "request.go",
+ "response.go",
+ "resty.go",
+ "retry.go",
+ "trace.go",
+ "transport.go",
+ "transport112.go",
+ "util.go",
+ ],
+ importpath = "github.com/go-resty/resty/v2",
+ visibility = ["//visibility:public"],
+ deps = ["@org_golang_x_net//publicsuffix:go_default_library"],
+)
+
+go_test(
+ name = "resty_test",
+ srcs = [
+ "client_test.go",
+ "context_test.go",
+ "example_test.go",
+ "request_test.go",
+ "resty_test.go",
+ "retry_test.go",
+ "util_test.go",
+ ],
+ data = glob([".testdata/*"]),
+ embed = [":resty"],
+ deps = ["@org_golang_x_net//proxy:go_default_library"],
+)
+
+alias(
+ name = "go_default_library",
+ actual = ":resty",
+ visibility = ["//visibility:public"],
+)
diff --git a/vendor/github.com/go-resty/resty/v2/LICENSE b/vendor/github.com/go-resty/resty/v2/LICENSE
new file mode 100644
index 000000000..27326a653
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015-2021 Jeevanandam M., https://myjeeva.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/go-resty/resty/v2/README.md b/vendor/github.com/go-resty/resty/v2/README.md
new file mode 100644
index 000000000..8ec651828
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/README.md
@@ -0,0 +1,906 @@
+
+
Resty
+Simple HTTP and REST client library for Go (inspired by Ruby rest-client)
+Features section describes in detail about Resty capabilities
+
+
+

+
+
+
Resty Communication Channels
+

+
+
+## News
+
+ * v2.7.0 [released](https://github.com/go-resty/resty/releases/tag/v2.7.0) and tagged on Nov 03, 2021.
+ * v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jul 16, 2019.
+ * v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
+ * v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its [contributors](https://github.com/go-resty/resty/graphs/contributors).
+
+## Features
+
+ * GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc.
+ * Simple and chainable methods for settings and request
+ * [Request](https://pkg.go.dev/github.com/go-resty/resty/v2#Request) Body can be `string`, `[]byte`, `struct`, `map`, `slice` and `io.Reader` too
+ * Auto detects `Content-Type`
+ * Buffer less processing for `io.Reader`
+ * Native `*http.Request` instance may be accessed during middleware and request execution via `Request.RawRequest`
+ * Request Body can be read multiple times via `Request.RawRequest.GetBody()`
+ * [Response](https://pkg.go.dev/github.com/go-resty/resty/v2#Response) object gives you more possibility
+ * Access as `[]byte` array - `response.Body()` OR Access as `string` - `response.String()`
+ * Know your `response.Time()` and when we `response.ReceivedAt()`
+ * Automatic marshal and unmarshal for `JSON` and `XML` content type
+ * Default is `JSON`, if you supply `struct/map` without header `Content-Type`
+ * For auto-unmarshal, refer to -
+ - Success scenario [Request.SetResult()](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetResult) and [Response.Result()](https://pkg.go.dev/github.com/go-resty/resty/v2#Response.Result).
+ - Error scenario [Request.SetError()](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetError) and [Response.Error()](https://pkg.go.dev/github.com/go-resty/resty/v2#Response.Error).
+ - Supports [RFC7807](https://tools.ietf.org/html/rfc7807) - `application/problem+json` & `application/problem+xml`
+ * Resty provides an option to override [JSON Marshal/Unmarshal and XML Marshal/Unmarshal](#override-json--xml-marshalunmarshal)
+ * Easy to upload one or more file(s) via `multipart/form-data`
+ * Auto detects file content type
+ * Request URL [Path Params (aka URI Params)](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetPathParams)
+ * Backoff Retry Mechanism with retry condition function [reference](retry_test.go)
+ * Resty client HTTP & REST [Request](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.OnBeforeRequest) and [Response](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.OnAfterResponse) middlewares
+ * `Request.SetContext` supported
+ * Authorization option of `BasicAuth` and `Bearer` token
+ * Set request `ContentLength` value for all request or particular request
+ * Custom [Root Certificates](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetRootCertificate) and Client [Certificates](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetCertificates)
+ * Download/Save HTTP response directly into File, like `curl -o` flag. See [SetOutputDirectory](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetOutputDirectory) & [SetOutput](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetOutput).
+ * Cookies for your request and CookieJar support
+ * SRV Record based request instead of Host URL
+ * Client settings like `Timeout`, `RedirectPolicy`, `Proxy`, `TLSClientConfig`, `Transport`, etc.
+ * Optionally allows GET request with payload, see [SetAllowGetMethodPayload](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetAllowGetMethodPayload)
+ * Supports registering external JSON library into resty, see [how to use](https://github.com/go-resty/resty/issues/76#issuecomment-314015250)
+ * Exposes Response reader without reading response (no auto-unmarshaling) if need be, see [how to use](https://github.com/go-resty/resty/issues/87#issuecomment-322100604)
+ * Option to specify expected `Content-Type` when response `Content-Type` header missing. Refer to [#92](https://github.com/go-resty/resty/issues/92)
+ * Resty design
+ * Have client level settings & options and also override at Request level if you want to
+ * Request and Response middleware
+ * Create Multiple clients if you want to `resty.New()`
+ * Supports `http.RoundTripper` implementation, see [SetTransport](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetTransport)
+ * goroutine concurrent safe
+ * Resty Client trace, see [Client.EnableTrace](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.EnableTrace) and [Request.EnableTrace](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.EnableTrace)
+ * Since v2.4.0, trace info contains a `RequestAttempt` value, and the `Request` object contains an `Attempt` attribute
+ * Debug mode - clean and informative logging presentation
+ * Gzip - Go does it automatically also resty has fallback handling too
+ * Works fine with `HTTP/2` and `HTTP/1.1`
+ * [Bazel support](#bazel-support)
+ * Easily mock Resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library)
+ * Well tested client library
+
+### Included Batteries
+
+ * Redirect Policies - see [how to use](#redirect-policy)
+ * NoRedirectPolicy
+ * FlexibleRedirectPolicy
+ * DomainCheckRedirectPolicy
+ * etc. [more info](redirect.go)
+ * Retry Mechanism [how to use](#retries)
+ * Backoff Retry
+ * Conditional Retry
+ * Since v2.6.0, Retry Hooks - [Client](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.AddRetryHook), [Request](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.AddRetryHook)
+ * SRV Record based request instead of Host URL [how to use](resty_test.go#L1412)
+ * etc (upcoming - throw your idea's [here](https://github.com/go-resty/resty/issues)).
+
+
+#### Supported Go Versions
+
+Initially Resty started supporting `go modules` since `v1.10.0` release.
+
+Starting Resty v2 and higher versions, it fully embraces [go modules](https://github.com/golang/go/wiki/Modules) package release. It requires a Go version capable of understanding `/vN` suffixed imports:
+
+- 1.9.7+
+- 1.10.3+
+- 1.11+
+
+
+## It might be beneficial for your project :smile:
+
+Resty author also published following projects for Go Community.
+
+ * [aah framework](https://aahframework.org) - A secure, flexible, rapid Go web framework.
+ * [THUMBAI](https://thumbai.app) - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
+ * [go-model](https://github.com/jeevatkm/go-model) - Robust & Easy to use model mapper and utility methods for Go `struct`.
+
+
+## Installation
+
+```bash
+# Go Modules
+require github.com/go-resty/resty/v2 v2.7.0
+```
+
+## Usage
+
+The following samples will assist you to become as comfortable as possible with resty library.
+
+```go
+// Import resty into your code and refer it as `resty`.
+import "github.com/go-resty/resty/v2"
+```
+
+#### Simple GET
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+resp, err := client.R().
+ EnableTrace().
+ Get("https://httpbin.org/get")
+
+// Explore response object
+fmt.Println("Response Info:")
+fmt.Println(" Error :", err)
+fmt.Println(" Status Code:", resp.StatusCode())
+fmt.Println(" Status :", resp.Status())
+fmt.Println(" Proto :", resp.Proto())
+fmt.Println(" Time :", resp.Time())
+fmt.Println(" Received At:", resp.ReceivedAt())
+fmt.Println(" Body :\n", resp)
+fmt.Println()
+
+// Explore trace info
+fmt.Println("Request Trace Info:")
+ti := resp.Request.TraceInfo()
+fmt.Println(" DNSLookup :", ti.DNSLookup)
+fmt.Println(" ConnTime :", ti.ConnTime)
+fmt.Println(" TCPConnTime :", ti.TCPConnTime)
+fmt.Println(" TLSHandshake :", ti.TLSHandshake)
+fmt.Println(" ServerTime :", ti.ServerTime)
+fmt.Println(" ResponseTime :", ti.ResponseTime)
+fmt.Println(" TotalTime :", ti.TotalTime)
+fmt.Println(" IsConnReused :", ti.IsConnReused)
+fmt.Println(" IsConnWasIdle :", ti.IsConnWasIdle)
+fmt.Println(" ConnIdleTime :", ti.ConnIdleTime)
+fmt.Println(" RequestAttempt:", ti.RequestAttempt)
+fmt.Println(" RemoteAddr :", ti.RemoteAddr.String())
+
+/* Output
+Response Info:
+ Error :
+ Status Code: 200
+ Status : 200 OK
+ Proto : HTTP/2.0
+ Time : 457.034718ms
+ Received At: 2020-09-14 15:35:29.784681 -0700 PDT m=+0.458137045
+ Body :
+ {
+ "args": {},
+ "headers": {
+ "Accept-Encoding": "gzip",
+ "Host": "httpbin.org",
+ "User-Agent": "go-resty/2.4.0 (https://github.com/go-resty/resty)",
+ "X-Amzn-Trace-Id": "Root=1-5f5ff031-000ff6292204aa6898e4de49"
+ },
+ "origin": "0.0.0.0",
+ "url": "https://httpbin.org/get"
+ }
+
+Request Trace Info:
+ DNSLookup : 4.074657ms
+ ConnTime : 381.709936ms
+ TCPConnTime : 77.428048ms
+ TLSHandshake : 299.623597ms
+ ServerTime : 75.414703ms
+ ResponseTime : 79.337µs
+ TotalTime : 457.034718ms
+ IsConnReused : false
+ IsConnWasIdle : false
+ ConnIdleTime : 0s
+ RequestAttempt: 1
+ RemoteAddr : 3.221.81.55:443
+*/
+```
+
+#### Enhanced GET
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+resp, err := client.R().
+ SetQueryParams(map[string]string{
+ "page_no": "1",
+ "limit": "20",
+ "sort":"name",
+ "order": "asc",
+ "random":strconv.FormatInt(time.Now().Unix(), 10),
+ }).
+ SetHeader("Accept", "application/json").
+ SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
+ Get("/search_result")
+
+
+// Sample of using Request.SetQueryString method
+resp, err := client.R().
+ SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more").
+ SetHeader("Accept", "application/json").
+ SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
+ Get("/show_product")
+
+
+// If necessary, you can force response content type to tell Resty to parse a JSON response into your struct
+resp, err := client.R().
+ SetResult(result).
+ ForceContentType("application/json").
+ Get("v2/alpine/manifests/latest")
+```
+
+#### Various POST method combinations
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// POST JSON string
+// No need to set content type, if you have client level setting
+resp, err := client.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(`{"username":"testuser", "password":"testpass"}`).
+ SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
+ Post("https://myapp.com/login")
+
+// POST []byte array
+// No need to set content type, if you have client level setting
+resp, err := client.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
+ SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
+ Post("https://myapp.com/login")
+
+// POST Struct, default is JSON content type. No need to set one
+resp, err := client.R().
+ SetBody(User{Username: "testuser", Password: "testpass"}).
+ SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
+ SetError(&AuthError{}). // or SetError(AuthError{}).
+ Post("https://myapp.com/login")
+
+// POST Map, default is JSON content type. No need to set one
+resp, err := client.R().
+ SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
+ SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
+ SetError(&AuthError{}). // or SetError(AuthError{}).
+ Post("https://myapp.com/login")
+
+// POST of raw bytes for file upload. For example: upload file to Dropbox
+fileBytes, _ := ioutil.ReadFile("/Users/jeeva/mydocument.pdf")
+
+// See we are not setting content-type header, since go-resty automatically detects Content-Type for you
+resp, err := client.R().
+ SetBody(fileBytes).
+ SetContentLength(true). // Dropbox expects this value
+ SetAuthToken("").
+ SetError(&DropboxError{}). // or SetError(DropboxError{}).
+ Post("https://content.dropboxapi.com/1/files_put/auto/resty/mydocument.pdf") // for upload Dropbox supports PUT too
+
+// Note: resty detects Content-Type for request body/payload if content type header is not set.
+// * For struct and map data type defaults to 'application/json'
+// * Fallback is plain text content type
+```
+
+#### Sample PUT
+
+You can use various combinations of `PUT` method call like demonstrated for `POST`.
+
+```go
+// Note: This is one sample of PUT method usage, refer POST for more combination
+
+// Create a Resty Client
+client := resty.New()
+
+// Request goes as JSON content type
+// No need to set auth token, error, if you have client level settings
+resp, err := client.R().
+ SetBody(Article{
+ Title: "go-resty",
+ Content: "This is my article content, oh ya!",
+ Author: "Jeevanandam M",
+ Tags: []string{"article", "sample", "resty"},
+ }).
+ SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
+ SetError(&Error{}). // or SetError(Error{}).
+ Put("https://myapp.com/article/1234")
+```
+
+#### Sample PATCH
+
+You can use various combinations of `PATCH` method call like demonstrated for `POST`.
+
+```go
+// Note: This is one sample of PUT method usage, refer POST for more combination
+
+// Create a Resty Client
+client := resty.New()
+
+// Request goes as JSON content type
+// No need to set auth token, error, if you have client level settings
+resp, err := client.R().
+ SetBody(Article{
+ Tags: []string{"new tag1", "new tag2"},
+ }).
+ SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
+ SetError(&Error{}). // or SetError(Error{}).
+ Patch("https://myapp.com/articles/1234")
+```
+
+#### Sample DELETE, HEAD, OPTIONS
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// DELETE a article
+// No need to set auth token, error, if you have client level settings
+resp, err := client.R().
+ SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
+ SetError(&Error{}). // or SetError(Error{}).
+ Delete("https://myapp.com/articles/1234")
+
+// DELETE a articles with payload/body as a JSON string
+// No need to set auth token, error, if you have client level settings
+resp, err := client.R().
+ SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
+ SetError(&Error{}). // or SetError(Error{}).
+ SetHeader("Content-Type", "application/json").
+ SetBody(`{article_ids: [1002, 1006, 1007, 87683, 45432] }`).
+ Delete("https://myapp.com/articles")
+
+// HEAD of resource
+// No need to set auth token, if you have client level settings
+resp, err := client.R().
+ SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
+ Head("https://myapp.com/videos/hi-res-video")
+
+// OPTIONS of resource
+// No need to set auth token, if you have client level settings
+resp, err := client.R().
+ SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
+ Options("https://myapp.com/servers/nyc-dc-01")
+```
+
+#### Override JSON & XML Marshal/Unmarshal
+
+User could register choice of JSON/XML library into resty or write your own. By default resty registers standard `encoding/json` and `encoding/xml` respectively.
+```go
+// Example of registering json-iterator
+import jsoniter "github.com/json-iterator/go"
+
+json := jsoniter.ConfigCompatibleWithStandardLibrary
+
+client := resty.New()
+client.JSONMarshal = json.Marshal
+client.JSONUnmarshal = json.Unmarshal
+
+// similarly user could do for XML too with -
+client.XMLMarshal
+client.XMLUnmarshal
+```
+
+### Multipart File(s) upload
+
+#### Using io.Reader
+
+```go
+profileImgBytes, _ := ioutil.ReadFile("/Users/jeeva/test-img.png")
+notesBytes, _ := ioutil.ReadFile("/Users/jeeva/text-file.txt")
+
+// Create a Resty Client
+client := resty.New()
+
+resp, err := client.R().
+ SetFileReader("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)).
+ SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
+ SetFormData(map[string]string{
+ "first_name": "Jeevanandam",
+ "last_name": "M",
+ }).
+ Post("http://myapp.com/upload")
+```
+
+#### Using File directly from Path
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Single file scenario
+resp, err := client.R().
+ SetFile("profile_img", "/Users/jeeva/test-img.png").
+ Post("http://myapp.com/upload")
+
+// Multiple files scenario
+resp, err := client.R().
+ SetFiles(map[string]string{
+ "profile_img": "/Users/jeeva/test-img.png",
+ "notes": "/Users/jeeva/text-file.txt",
+ }).
+ Post("http://myapp.com/upload")
+
+// Multipart of form fields and files
+resp, err := client.R().
+ SetFiles(map[string]string{
+ "profile_img": "/Users/jeeva/test-img.png",
+ "notes": "/Users/jeeva/text-file.txt",
+ }).
+ SetFormData(map[string]string{
+ "first_name": "Jeevanandam",
+ "last_name": "M",
+ "zip_code": "00001",
+ "city": "my city",
+ "access_token": "C6A79608-782F-4ED0-A11D-BD82FAD829CD",
+ }).
+ Post("http://myapp.com/profile")
+```
+
+#### Sample Form submission
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// just mentioning about POST as an example with simple flow
+// User Login
+resp, err := client.R().
+ SetFormData(map[string]string{
+ "username": "jeeva",
+ "password": "mypass",
+ }).
+ Post("http://myapp.com/login")
+
+// Followed by profile update
+resp, err := client.R().
+ SetFormData(map[string]string{
+ "first_name": "Jeevanandam",
+ "last_name": "M",
+ "zip_code": "00001",
+ "city": "new city update",
+ }).
+ Post("http://myapp.com/profile")
+
+// Multi value form data
+criteria := url.Values{
+ "search_criteria": []string{"book", "glass", "pencil"},
+}
+resp, err := client.R().
+ SetFormDataFromValues(criteria).
+ Post("http://myapp.com/search")
+```
+
+#### Save HTTP Response into File
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Setting output directory path, If directory not exists then resty creates one!
+// This is optional one, if you're planning using absoule path in
+// `Request.SetOutput` and can used together.
+client.SetOutputDirectory("/Users/jeeva/Downloads")
+
+// HTTP response gets saved into file, similar to curl -o flag
+_, err := client.R().
+ SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip").
+ Get("http://bit.ly/1LouEKr")
+
+// OR using absolute path
+// Note: output directory path is not used for absolute path
+_, err := client.R().
+ SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip").
+ Get("http://bit.ly/1LouEKr")
+```
+
+#### Request URL Path Params
+
+Resty provides easy to use dynamic request URL path params. Params can be set at client and request level. Client level params value can be overridden at request level.
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+client.R().SetPathParams(map[string]string{
+ "userId": "sample@sample.com",
+ "subAccountId": "100002",
+}).
+Get("/v1/users/{userId}/{subAccountId}/details")
+
+// Result:
+// Composed URL - /v1/users/sample@sample.com/100002/details
+```
+
+#### Request and Response Middleware
+
+Resty provides middleware ability to manipulate for Request and Response. It is more flexible than callback approach.
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Registering Request Middleware
+client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
+ // Now you have access to Client and current Request object
+ // manipulate it as per your need
+
+ return nil // if its success otherwise return error
+ })
+
+// Registering Response Middleware
+client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
+ // Now you have access to Client and current Response object
+ // manipulate it as per your need
+
+ return nil // if its success otherwise return error
+ })
+```
+
+#### OnError Hooks
+
+Resty provides OnError hooks that may be called because:
+
+- The client failed to send the request due to connection timeout, TLS handshake failure, etc...
+- The request was retried the maximum amount of times, and still failed.
+
+If there was a response from the server, the original error will be wrapped in `*resty.ResponseError` which contains the last response received.
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+client.OnError(func(req *resty.Request, err error) {
+ if v, ok := err.(*resty.ResponseError); ok {
+ // v.Response contains the last response from the server
+ // v.Err contains the original error
+ }
+ // Log the error, increment a metric, etc...
+})
+```
+
+#### Redirect Policy
+
+Resty provides few ready to use redirect policy(s) also it supports multiple policies together.
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Assign Client Redirect Policy. Create one as per you need
+client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
+
+// Wanna multiple policies such as redirect count, domain name check, etc
+client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
+ resty.DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
+```
+
+##### Custom Redirect Policy
+
+Implement [RedirectPolicy](redirect.go#L20) interface and register it with resty client. Have a look [redirect.go](redirect.go) for more information.
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Using raw func into resty.SetRedirectPolicy
+client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
+ // Implement your logic here
+
+ // return nil for continue redirect otherwise return error to stop/prevent redirect
+ return nil
+}))
+
+//---------------------------------------------------
+
+// Using struct create more flexible redirect policy
+type CustomRedirectPolicy struct {
+ // variables goes here
+}
+
+func (c *CustomRedirectPolicy) Apply(req *http.Request, via []*http.Request) error {
+ // Implement your logic here
+
+ // return nil for continue redirect otherwise return error to stop/prevent redirect
+ return nil
+}
+
+// Registering in resty
+client.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})
+```
+
+#### Custom Root Certificates and Client Certificates
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Custom Root certificates, just supply .pem file.
+// you can add one or more root certificates, its get appended
+client.SetRootCertificate("/path/to/root/pemFile1.pem")
+client.SetRootCertificate("/path/to/root/pemFile2.pem")
+// ... and so on!
+
+// Adding Client Certificates, you add one or more certificates
+// Sample for creating certificate object
+// Parsing public/private key pair from a pair of files. The files must contain PEM encoded data.
+cert1, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
+if err != nil {
+ log.Fatalf("ERROR client certificate: %s", err)
+}
+// ...
+
+// You add one or more certificates
+client.SetCertificates(cert1, cert2, cert3)
+```
+
+#### Custom Root Certificates and Client Certificates from string
+
+```go
+// Custom Root certificates from string
+// You can pass you certificates throught env variables as strings
+// you can add one or more root certificates, its get appended
+client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
+client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
+// ... and so on!
+
+// Adding Client Certificates, you add one or more certificates
+// Sample for creating certificate object
+// Parsing public/private key pair from a pair of files. The files must contain PEM encoded data.
+cert1, err := tls.X509KeyPair([]byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"), []byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"))
+if err != nil {
+ log.Fatalf("ERROR client certificate: %s", err)
+}
+// ...
+
+// You add one or more certificates
+client.SetCertificates(cert1, cert2, cert3)
+```
+
+#### Proxy Settings - Client as well as at Request Level
+
+Default `Go` supports Proxy via environment variable `HTTP_PROXY`. Resty provides support via `SetProxy` & `RemoveProxy`.
+Choose as per your need.
+
+**Client Level Proxy** settings applied to all the request
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Setting a Proxy URL and Port
+client.SetProxy("http://proxyserver:8888")
+
+// Want to remove proxy setting
+client.RemoveProxy()
+```
+
+#### Retries
+
+Resty uses [backoff](http://www.awsarchitectureblog.com/2015/03/backoff.html)
+to increase retry intervals after each attempt.
+
+Usage example:
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Retries are configured per client
+client.
+ // Set retry count to non zero to enable retries
+ SetRetryCount(3).
+ // You can override initial retry wait time.
+ // Default is 100 milliseconds.
+ SetRetryWaitTime(5 * time.Second).
+ // MaxWaitTime can be overridden as well.
+ // Default is 2 seconds.
+ SetRetryMaxWaitTime(20 * time.Second).
+ // SetRetryAfter sets callback to calculate wait time between retries.
+ // Default (nil) implies exponential backoff with jitter
+ SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ })
+```
+
+Above setup will result in resty retrying requests returned non nil error up to
+3 times with delay increased after each attempt.
+
+You can optionally provide client with [custom retry conditions](https://pkg.go.dev/github.com/go-resty/resty/v2#RetryConditionFunc):
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+client.AddRetryCondition(
+ // RetryConditionFunc type is for retry condition function
+ // input: non-nil Response OR request execution error
+ func(r *resty.Response, err error) bool {
+ return r.StatusCode() == http.StatusTooManyRequests
+ },
+)
+```
+
+Above example will make resty retry requests ended with `429 Too Many Requests`
+status code.
+
+Multiple retry conditions can be added.
+
+It is also possible to use `resty.Backoff(...)` to get arbitrary retry scenarios
+implemented. [Reference](retry_test.go).
+
+#### Allow GET request with Payload
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Allow GET request with Payload. This is disabled by default.
+client.SetAllowGetMethodPayload(true)
+```
+
+#### Wanna Multiple Clients
+
+```go
+// Here you go!
+// Client 1
+client1 := resty.New()
+client1.R().Get("http://httpbin.org")
+// ...
+
+// Client 2
+client2 := resty.New()
+client2.R().Head("http://httpbin.org")
+// ...
+
+// Bend it as per your need!!!
+```
+
+#### Remaining Client Settings & its Options
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Unique settings at Client level
+//--------------------------------
+// Enable debug mode
+client.SetDebug(true)
+
+// Assign Client TLSClientConfig
+// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
+client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
+
+// or One can disable security check (https)
+client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+
+// Set client timeout as per your need
+client.SetTimeout(1 * time.Minute)
+
+
+// You can override all below settings and options at request level if you want to
+//--------------------------------------------------------------------------------
+// Host URL for all request. So you can use relative URL in the request
+client.SetHostURL("http://httpbin.org")
+
+// Headers for all request
+client.SetHeader("Accept", "application/json")
+client.SetHeaders(map[string]string{
+ "Content-Type": "application/json",
+ "User-Agent": "My custom User Agent String",
+ })
+
+// Cookies for all request
+client.SetCookie(&http.Cookie{
+ Name:"go-resty",
+ Value:"This is cookie value",
+ Path: "/",
+ Domain: "sample.com",
+ MaxAge: 36000,
+ HttpOnly: true,
+ Secure: false,
+ })
+client.SetCookies(cookies)
+
+// URL query parameters for all request
+client.SetQueryParam("user_id", "00001")
+client.SetQueryParams(map[string]string{ // sample of those who use this manner
+ "api_key": "api-key-here",
+ "api_secert": "api-secert",
+ })
+client.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
+
+// Form data for all request. Typically used with POST and PUT
+client.SetFormData(map[string]string{
+ "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
+ })
+
+// Basic Auth for all request
+client.SetBasicAuth("myuser", "mypass")
+
+// Bearer Auth Token for all request
+client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
+
+// Enabling Content length value for all request
+client.SetContentLength(true)
+
+// Registering global Error object structure for JSON/XML request
+client.SetError(&Error{}) // or resty.SetError(Error{})
+```
+
+#### Unix Socket
+
+```go
+unixSocket := "/var/run/my_socket.sock"
+
+// Create a Go's http.Transport so we can set it in resty.
+transport := http.Transport{
+ Dial: func(_, _ string) (net.Conn, error) {
+ return net.Dial("unix", unixSocket)
+ },
+}
+
+// Create a Resty Client
+client := resty.New()
+
+// Set the previous transport that we created, set the scheme of the communication to the
+// socket and set the unixSocket as the HostURL.
+client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
+
+// No need to write the host's URL on the request, just the path.
+client.R().Get("/index.html")
+```
+
+#### Bazel Support
+
+Resty can be built, tested and depended upon via [Bazel](https://bazel.build).
+For example, to run all tests:
+
+```shell
+bazel test :resty_test
+```
+
+#### Mocking http requests using [httpmock](https://github.com/jarcoal/httpmock) library
+
+In order to mock the http requests when testing your application you
+could use the `httpmock` library.
+
+When using the default resty client, you should pass the client to the library as follow:
+
+```go
+// Create a Resty Client
+client := resty.New()
+
+// Get the underlying HTTP Client and set it to Mock
+httpmock.ActivateNonDefault(client.GetClient())
+```
+
+More detailed example of mocking resty http requests using ginko could be found [here](https://github.com/jarcoal/httpmock#ginkgo--resty-example).
+
+## Versioning
+
+Resty releases versions according to [Semantic Versioning](http://semver.org)
+
+ * Resty v2 does not use `gopkg.in` service for library versioning.
+ * Resty fully adapted to `go mod` capabilities since `v1.10.0` release.
+ * Resty v1 series was using `gopkg.in` to provide versioning. `gopkg.in/resty.vX` points to appropriate tagged versions; `X` denotes version series number and it's a stable release for production use. For e.g. `gopkg.in/resty.v0`.
+ * Development takes place at the master branch. Although the code in master should always compile and test successfully, it might break API's. I aim to maintain backwards compatibility, but sometimes API's and behavior might be changed to fix a bug.
+
+## Contribution
+
+I would welcome your contribution! If you find any improvement or issue you want to fix, feel free to send a pull request, I like pull requests that include test cases for fix/enhancement. I have done my best to bring pretty good code coverage. Feel free to write tests.
+
+BTW, I'd like to know what you think about `Resty`. Kindly open an issue or send me an email; it'd mean a lot to me.
+
+## Creator
+
+[Jeevanandam M.](https://github.com/jeevatkm) (jeeva@myjeeva.com)
+
+## Core Team
+
+Have a look on [Members](https://github.com/orgs/go-resty/people) page.
+
+## Contributors
+
+Have a look on [Contributors](https://github.com/go-resty/resty/graphs/contributors) page.
+
+## License
+
+Resty released under MIT license, refer [LICENSE](LICENSE) file.
diff --git a/vendor/github.com/go-resty/resty/v2/WORKSPACE b/vendor/github.com/go-resty/resty/v2/WORKSPACE
new file mode 100644
index 000000000..9ef03e95a
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/WORKSPACE
@@ -0,0 +1,31 @@
+workspace(name = "resty")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+ name = "io_bazel_rules_go",
+ sha256 = "69de5c704a05ff37862f7e0f5534d4f479418afc21806c887db544a316f3cb6b",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.27.0/rules_go-v0.27.0.tar.gz",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.27.0/rules_go-v0.27.0.tar.gz",
+ ],
+)
+
+http_archive(
+ name = "bazel_gazelle",
+ sha256 = "62ca106be173579c0a167deb23358fdfe71ffa1e4cfdddf5582af26520f1c66f",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.0.tar.gz",
+ ],
+)
+
+load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
+
+go_rules_dependencies()
+
+go_register_toolchains(version = "1.16")
+
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
+
+gazelle_dependencies()
diff --git a/vendor/github.com/go-resty/resty/v2/client.go b/vendor/github.com/go-resty/resty/v2/client.go
new file mode 100644
index 000000000..1a03efa37
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/client.go
@@ -0,0 +1,1115 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "bytes"
+ "compress/gzip"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math"
+ "net/http"
+ "net/url"
+ "reflect"
+ "regexp"
+ "strings"
+ "sync"
+ "time"
+)
+
+const (
+ // MethodGet HTTP method
+ MethodGet = "GET"
+
+ // MethodPost HTTP method
+ MethodPost = "POST"
+
+ // MethodPut HTTP method
+ MethodPut = "PUT"
+
+ // MethodDelete HTTP method
+ MethodDelete = "DELETE"
+
+ // MethodPatch HTTP method
+ MethodPatch = "PATCH"
+
+ // MethodHead HTTP method
+ MethodHead = "HEAD"
+
+ // MethodOptions HTTP method
+ MethodOptions = "OPTIONS"
+)
+
+var (
+ hdrUserAgentKey = http.CanonicalHeaderKey("User-Agent")
+ hdrAcceptKey = http.CanonicalHeaderKey("Accept")
+ hdrContentTypeKey = http.CanonicalHeaderKey("Content-Type")
+ hdrContentLengthKey = http.CanonicalHeaderKey("Content-Length")
+ hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding")
+ hdrLocationKey = http.CanonicalHeaderKey("Location")
+
+ plainTextType = "text/plain; charset=utf-8"
+ jsonContentType = "application/json"
+ formContentType = "application/x-www-form-urlencoded"
+
+ jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json\-.*)(;|$))`)
+ xmlCheck = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`)
+
+ hdrUserAgentValue = "go-resty/" + Version + " (https://github.com/go-resty/resty)"
+ bufPool = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
+)
+
+type (
+ // RequestMiddleware type is for request middleware, called before a request is sent
+ RequestMiddleware func(*Client, *Request) error
+
+ // ResponseMiddleware type is for response middleware, called after a response has been received
+ ResponseMiddleware func(*Client, *Response) error
+
+ // PreRequestHook type is for the request hook, called right before the request is sent
+ PreRequestHook func(*Client, *http.Request) error
+
+ // RequestLogCallback type is for request logs, called before the request is logged
+ RequestLogCallback func(*RequestLog) error
+
+ // ResponseLogCallback type is for response logs, called before the response is logged
+ ResponseLogCallback func(*ResponseLog) error
+
+ // ErrorHook type is for reacting to request errors, called after all retries were attempted
+ ErrorHook func(*Request, error)
+)
+
+// Client struct is used to create Resty client with client level settings,
+// these settings are applicable to all the request raised from the client.
+//
+// Resty also provides an options to override most of the client settings
+// at request level.
+type Client struct {
+ BaseURL string
+ HostURL string // Deprecated: use BaseURL instead. To be removed in v3.0.0 release.
+ QueryParam url.Values
+ FormData url.Values
+ PathParams map[string]string
+ Header http.Header
+ UserInfo *User
+ Token string
+ AuthScheme string
+ Cookies []*http.Cookie
+ Error reflect.Type
+ Debug bool
+ DisableWarn bool
+ AllowGetMethodPayload bool
+ RetryCount int
+ RetryWaitTime time.Duration
+ RetryMaxWaitTime time.Duration
+ RetryConditions []RetryConditionFunc
+ RetryHooks []OnRetryFunc
+ RetryAfter RetryAfterFunc
+ JSONMarshal func(v interface{}) ([]byte, error)
+ JSONUnmarshal func(data []byte, v interface{}) error
+ XMLMarshal func(v interface{}) ([]byte, error)
+ XMLUnmarshal func(data []byte, v interface{}) error
+
+ // HeaderAuthorizationKey is used to set/access Request Authorization header
+ // value when `SetAuthToken` option is used.
+ HeaderAuthorizationKey string
+
+ jsonEscapeHTML bool
+ setContentLength bool
+ closeConnection bool
+ notParseResponse bool
+ trace bool
+ debugBodySizeLimit int64
+ outputDirectory string
+ scheme string
+ log Logger
+ httpClient *http.Client
+ proxyURL *url.URL
+ beforeRequest []RequestMiddleware
+ udBeforeRequest []RequestMiddleware
+ preReqHook PreRequestHook
+ afterResponse []ResponseMiddleware
+ requestLog RequestLogCallback
+ responseLog ResponseLogCallback
+ errorHooks []ErrorHook
+}
+
+// User type is to hold an username and password information
+type User struct {
+ Username, Password string
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Client methods
+//___________________________________
+
+// SetHostURL method is to set Host URL in the client instance. It will be used with request
+// raised from this client with relative URL
+// // Setting HTTP address
+// client.SetHostURL("http://myjeeva.com")
+//
+// // Setting HTTPS address
+// client.SetHostURL("https://myjeeva.com")
+//
+// Deprecated: use SetBaseURL instead. To be removed in v3.0.0 release.
+func (c *Client) SetHostURL(url string) *Client {
+ c.SetBaseURL(url)
+ return c
+}
+
+// SetBaseURL method is to set Base URL in the client instance. It will be used with request
+// raised from this client with relative URL
+// // Setting HTTP address
+// client.SetBaseURL("http://myjeeva.com")
+//
+// // Setting HTTPS address
+// client.SetBaseURL("https://myjeeva.com")
+//
+// Since v2.7.0
+func (c *Client) SetBaseURL(url string) *Client {
+ c.BaseURL = strings.TrimRight(url, "/")
+ c.HostURL = c.BaseURL
+ return c
+}
+
+// SetHeader method sets a single header field and its value in the client instance.
+// These headers will be applied to all requests raised from this client instance.
+// Also it can be overridden at request level header options.
+//
+// See `Request.SetHeader` or `Request.SetHeaders`.
+//
+// For Example: To set `Content-Type` and `Accept` as `application/json`
+//
+// client.
+// SetHeader("Content-Type", "application/json").
+// SetHeader("Accept", "application/json")
+func (c *Client) SetHeader(header, value string) *Client {
+ c.Header.Set(header, value)
+ return c
+}
+
+// SetHeaders method sets multiple headers field and its values at one go in the client instance.
+// These headers will be applied to all requests raised from this client instance. Also it can be
+// overridden at request level headers options.
+//
+// See `Request.SetHeaders` or `Request.SetHeader`.
+//
+// For Example: To set `Content-Type` and `Accept` as `application/json`
+//
+// client.SetHeaders(map[string]string{
+// "Content-Type": "application/json",
+// "Accept": "application/json",
+// })
+func (c *Client) SetHeaders(headers map[string]string) *Client {
+ for h, v := range headers {
+ c.Header.Set(h, v)
+ }
+ return c
+}
+
+// SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.
+//
+// For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
+// client.R().
+// SetHeaderVerbatim("all_lowercase", "available").
+// SetHeaderVerbatim("UPPERCASE", "available")
+//
+// Also you can override header value, which was set at client instance level.
+//
+// Since v2.6.0
+func (c *Client) SetHeaderVerbatim(header, value string) *Client {
+ c.Header[header] = []string{value}
+ return c
+}
+
+// SetCookieJar method sets custom http.CookieJar in the resty client. Its way to override default.
+//
+// For Example: sometimes we don't want to save cookies in api contacting, we can remove the default
+// CookieJar in resty client.
+//
+// client.SetCookieJar(nil)
+func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
+ c.httpClient.Jar = jar
+ return c
+}
+
+// SetCookie method appends a single cookie in the client instance.
+// These cookies will be added to all the request raised from this client instance.
+// client.SetCookie(&http.Cookie{
+// Name:"go-resty",
+// Value:"This is cookie value",
+// })
+func (c *Client) SetCookie(hc *http.Cookie) *Client {
+ c.Cookies = append(c.Cookies, hc)
+ return c
+}
+
+// SetCookies method sets an array of cookies in the client instance.
+// These cookies will be added to all the request raised from this client instance.
+// cookies := []*http.Cookie{
+// &http.Cookie{
+// Name:"go-resty-1",
+// Value:"This is cookie 1 value",
+// },
+// &http.Cookie{
+// Name:"go-resty-2",
+// Value:"This is cookie 2 value",
+// },
+// }
+//
+// // Setting a cookies into resty
+// client.SetCookies(cookies)
+func (c *Client) SetCookies(cs []*http.Cookie) *Client {
+ c.Cookies = append(c.Cookies, cs...)
+ return c
+}
+
+// SetQueryParam method sets single parameter and its value in the client instance.
+// It will be formed as query string for the request.
+//
+// For Example: `search=kitchen%20papers&size=large`
+// in the URL after `?` mark. These query params will be added to all the request raised from
+// this client instance. Also it can be overridden at request level Query Param options.
+//
+// See `Request.SetQueryParam` or `Request.SetQueryParams`.
+// client.
+// SetQueryParam("search", "kitchen papers").
+// SetQueryParam("size", "large")
+func (c *Client) SetQueryParam(param, value string) *Client {
+ c.QueryParam.Set(param, value)
+ return c
+}
+
+// SetQueryParams method sets multiple parameters and their values at one go in the client instance.
+// It will be formed as query string for the request.
+//
+// For Example: `search=kitchen%20papers&size=large`
+// in the URL after `?` mark. These query params will be added to all the request raised from this
+// client instance. Also it can be overridden at request level Query Param options.
+//
+// See `Request.SetQueryParams` or `Request.SetQueryParam`.
+// client.SetQueryParams(map[string]string{
+// "search": "kitchen papers",
+// "size": "large",
+// })
+func (c *Client) SetQueryParams(params map[string]string) *Client {
+ for p, v := range params {
+ c.SetQueryParam(p, v)
+ }
+ return c
+}
+
+// SetFormData method sets Form parameters and their values in the client instance.
+// It's applicable only HTTP method `POST` and `PUT` and requets content type would be set as
+// `application/x-www-form-urlencoded`. These form data will be added to all the request raised from
+// this client instance. Also it can be overridden at request level form data.
+//
+// See `Request.SetFormData`.
+// client.SetFormData(map[string]string{
+// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
+// "user_id": "3455454545",
+// })
+func (c *Client) SetFormData(data map[string]string) *Client {
+ for k, v := range data {
+ c.FormData.Set(k, v)
+ }
+ return c
+}
+
+// SetBasicAuth method sets the basic authentication header in the HTTP request. For Example:
+// Authorization: Basic
+//
+// For Example: To set the header for username "go-resty" and password "welcome"
+// client.SetBasicAuth("go-resty", "welcome")
+//
+// This basic auth information gets added to all the request rasied from this client instance.
+// Also it can be overridden or set one at the request level is supported.
+//
+// See `Request.SetBasicAuth`.
+func (c *Client) SetBasicAuth(username, password string) *Client {
+ c.UserInfo = &User{Username: username, Password: password}
+ return c
+}
+
+// SetAuthToken method sets the auth token of the `Authorization` header for all HTTP requests.
+// The default auth scheme is `Bearer`, it can be customized with the method `SetAuthScheme`. For Example:
+// Authorization:
+//
+// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
+//
+// client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
+//
+// This auth token gets added to all the requests rasied from this client instance.
+// Also it can be overridden or set one at the request level is supported.
+//
+// See `Request.SetAuthToken`.
+func (c *Client) SetAuthToken(token string) *Client {
+ c.Token = token
+ return c
+}
+
+// SetAuthScheme method sets the auth scheme type in the HTTP request. For Example:
+// Authorization:
+//
+// For Example: To set the scheme to use OAuth
+//
+// client.SetAuthScheme("OAuth")
+//
+// This auth scheme gets added to all the requests rasied from this client instance.
+// Also it can be overridden or set one at the request level is supported.
+//
+// Information about auth schemes can be found in RFC7235 which is linked to below
+// along with the page containing the currently defined official authentication schemes:
+// https://tools.ietf.org/html/rfc7235
+// https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
+//
+// See `Request.SetAuthToken`.
+func (c *Client) SetAuthScheme(scheme string) *Client {
+ c.AuthScheme = scheme
+ return c
+}
+
+// R method creates a new request instance, its used for Get, Post, Put, Delete, Patch, Head, Options, etc.
+func (c *Client) R() *Request {
+ r := &Request{
+ QueryParam: url.Values{},
+ FormData: url.Values{},
+ Header: http.Header{},
+ Cookies: make([]*http.Cookie, 0),
+
+ client: c,
+ multipartFiles: []*File{},
+ multipartFields: []*MultipartField{},
+ PathParams: map[string]string{},
+ jsonEscapeHTML: true,
+ }
+ return r
+}
+
+// NewRequest is an alias for method `R()`. Creates a new request instance, its used for
+// Get, Post, Put, Delete, Patch, Head, Options, etc.
+func (c *Client) NewRequest() *Request {
+ return c.R()
+}
+
+// OnBeforeRequest method appends request middleware into the before request chain.
+// Its gets applied after default Resty request middlewares and before request
+// been sent from Resty to host server.
+// client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
+// // Now you have access to Client and Request instance
+// // manipulate it as per your need
+//
+// return nil // if its success otherwise return error
+// })
+func (c *Client) OnBeforeRequest(m RequestMiddleware) *Client {
+ c.udBeforeRequest = append(c.udBeforeRequest, m)
+ return c
+}
+
+// OnAfterResponse method appends response middleware into the after response chain.
+// Once we receive response from host server, default Resty response middleware
+// gets applied and then user assigened response middlewares applied.
+// client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
+// // Now you have access to Client and Response instance
+// // manipulate it as per your need
+//
+// return nil // if its success otherwise return error
+// })
+func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client {
+ c.afterResponse = append(c.afterResponse, m)
+ return c
+}
+
+// OnError method adds a callback that will be run whenever a request execution fails.
+// This is called after all retries have been attempted (if any).
+// If there was a response from the server, the error will be wrapped in *ResponseError
+// which has the last response received from the server.
+//
+// client.OnError(func(req *resty.Request, err error) {
+// if v, ok := err.(*resty.ResponseError); ok {
+// // Do something with v.Response
+// }
+// // Log the error, increment a metric, etc...
+// })
+func (c *Client) OnError(h ErrorHook) *Client {
+ c.errorHooks = append(c.errorHooks, h)
+ return c
+}
+
+// SetPreRequestHook method sets the given pre-request function into resty client.
+// It is called right before the request is fired.
+//
+// Note: Only one pre-request hook can be registered. Use `client.OnBeforeRequest` for mutilple.
+func (c *Client) SetPreRequestHook(h PreRequestHook) *Client {
+ if c.preReqHook != nil {
+ c.log.Warnf("Overwriting an existing pre-request hook: %s", functionName(h))
+ }
+ c.preReqHook = h
+ return c
+}
+
+// SetDebug method enables the debug mode on Resty client. Client logs details of every request and response.
+// For `Request` it logs information such as HTTP verb, Relative URL path, Host, Headers, Body if it has one.
+// For `Response` it logs information such as Status, Response Time, Headers, Body if it has one.
+// client.SetDebug(true)
+func (c *Client) SetDebug(d bool) *Client {
+ c.Debug = d
+ return c
+}
+
+// SetDebugBodyLimit sets the maximum size for which the response and request body will be logged in debug mode.
+// client.SetDebugBodyLimit(1000000)
+func (c *Client) SetDebugBodyLimit(sl int64) *Client {
+ c.debugBodySizeLimit = sl
+ return c
+}
+
+// OnRequestLog method used to set request log callback into Resty. Registered callback gets
+// called before the resty actually logs the information.
+func (c *Client) OnRequestLog(rl RequestLogCallback) *Client {
+ if c.requestLog != nil {
+ c.log.Warnf("Overwriting an existing on-request-log callback from=%s to=%s",
+ functionName(c.requestLog), functionName(rl))
+ }
+ c.requestLog = rl
+ return c
+}
+
+// OnResponseLog method used to set response log callback into Resty. Registered callback gets
+// called before the resty actually logs the information.
+func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client {
+ if c.responseLog != nil {
+ c.log.Warnf("Overwriting an existing on-response-log callback from=%s to=%s",
+ functionName(c.responseLog), functionName(rl))
+ }
+ c.responseLog = rl
+ return c
+}
+
+// SetDisableWarn method disables the warning message on Resty client.
+//
+// For Example: Resty warns the user when BasicAuth used on non-TLS mode.
+// client.SetDisableWarn(true)
+func (c *Client) SetDisableWarn(d bool) *Client {
+ c.DisableWarn = d
+ return c
+}
+
+// SetAllowGetMethodPayload method allows the GET method with payload on Resty client.
+//
+// For Example: Resty allows the user sends request with a payload on HTTP GET method.
+// client.SetAllowGetMethodPayload(true)
+func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
+ c.AllowGetMethodPayload = a
+ return c
+}
+
+// SetLogger method sets given writer for logging Resty request and response details.
+//
+// Compliant to interface `resty.Logger`.
+func (c *Client) SetLogger(l Logger) *Client {
+ c.log = l
+ return c
+}
+
+// SetContentLength method enables the HTTP header `Content-Length` value for every request.
+// By default Resty won't set `Content-Length`.
+// client.SetContentLength(true)
+//
+// Also you have an option to enable for particular request. See `Request.SetContentLength`
+func (c *Client) SetContentLength(l bool) *Client {
+ c.setContentLength = l
+ return c
+}
+
+// SetTimeout method sets timeout for request raised from client.
+// client.SetTimeout(time.Duration(1 * time.Minute))
+func (c *Client) SetTimeout(timeout time.Duration) *Client {
+ c.httpClient.Timeout = timeout
+ return c
+}
+
+// SetError method is to register the global or client common `Error` object into Resty.
+// It is used for automatic unmarshalling if response status code is greater than 399 and
+// content type either JSON or XML. Can be pointer or non-pointer.
+// client.SetError(&Error{})
+// // OR
+// client.SetError(Error{})
+func (c *Client) SetError(err interface{}) *Client {
+ c.Error = typeOf(err)
+ return c
+}
+
+// SetRedirectPolicy method sets the client redirect poilicy. Resty provides ready to use
+// redirect policies. Wanna create one for yourself refer to `redirect.go`.
+//
+// client.SetRedirectPolicy(FlexibleRedirectPolicy(20))
+//
+// // Need multiple redirect policies together
+// client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
+func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
+ for _, p := range policies {
+ if _, ok := p.(RedirectPolicy); !ok {
+ c.log.Errorf("%v does not implement resty.RedirectPolicy (missing Apply method)",
+ functionName(p))
+ }
+ }
+
+ c.httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+ for _, p := range policies {
+ if err := p.(RedirectPolicy).Apply(req, via); err != nil {
+ return err
+ }
+ }
+ return nil // looks good, go ahead
+ }
+
+ return c
+}
+
+// SetRetryCount method enables retry on Resty client and allows you
+// to set no. of retry count. Resty uses a Backoff mechanism.
+func (c *Client) SetRetryCount(count int) *Client {
+ c.RetryCount = count
+ return c
+}
+
+// SetRetryWaitTime method sets default wait time to sleep before retrying
+// request.
+//
+// Default is 100 milliseconds.
+func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
+ c.RetryWaitTime = waitTime
+ return c
+}
+
+// SetRetryMaxWaitTime method sets max wait time to sleep before retrying
+// request.
+//
+// Default is 2 seconds.
+func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
+ c.RetryMaxWaitTime = maxWaitTime
+ return c
+}
+
+// SetRetryAfter sets callback to calculate wait time between retries.
+// Default (nil) implies exponential backoff with jitter
+func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
+ c.RetryAfter = callback
+ return c
+}
+
+// AddRetryCondition method adds a retry condition function to array of functions
+// that are checked to determine if the request is retried. The request will
+// retry if any of the functions return true and error is nil.
+//
+// Note: These retry conditions are applied on all Request made using this Client.
+// For Request specific retry conditions check *Request.AddRetryCondition
+func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
+ c.RetryConditions = append(c.RetryConditions, condition)
+ return c
+}
+
+// AddRetryAfterErrorCondition adds the basic condition of retrying after encountering
+// an error from the http response
+//
+// Since v2.6.0
+func (c *Client) AddRetryAfterErrorCondition() *Client {
+ c.AddRetryCondition(func(response *Response, err error) bool {
+ return response.IsError()
+ })
+ return c
+}
+
+// AddRetryHook adds a side-effecting retry hook to an array of hooks
+// that will be executed on each retry.
+//
+// Since v2.6.0
+func (c *Client) AddRetryHook(hook OnRetryFunc) *Client {
+ c.RetryHooks = append(c.RetryHooks, hook)
+ return c
+}
+
+// SetTLSClientConfig method sets TLSClientConfig for underling client Transport.
+//
+// For Example:
+// // One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
+// client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
+//
+// // or One can disable security check (https)
+// client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+//
+// Note: This method overwrites existing `TLSClientConfig`.
+func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
+ transport, err := c.transport()
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+ transport.TLSClientConfig = config
+ return c
+}
+
+// SetProxy method sets the Proxy URL and Port for Resty client.
+// client.SetProxy("http://proxyserver:8888")
+//
+// OR Without this `SetProxy` method, you could also set Proxy via environment variable.
+//
+// Refer to godoc `http.ProxyFromEnvironment`.
+func (c *Client) SetProxy(proxyURL string) *Client {
+ transport, err := c.transport()
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+
+ pURL, err := url.Parse(proxyURL)
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+
+ c.proxyURL = pURL
+ transport.Proxy = http.ProxyURL(c.proxyURL)
+ return c
+}
+
+// RemoveProxy method removes the proxy configuration from Resty client
+// client.RemoveProxy()
+func (c *Client) RemoveProxy() *Client {
+ transport, err := c.transport()
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+ c.proxyURL = nil
+ transport.Proxy = nil
+ return c
+}
+
+// SetCertificates method helps to set client certificates into Resty conveniently.
+func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
+ config, err := c.tlsConfig()
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+ config.Certificates = append(config.Certificates, certs...)
+ return c
+}
+
+// SetRootCertificate method helps to add one or more root certificates into Resty client
+// client.SetRootCertificate("/path/to/root/pemFile.pem")
+func (c *Client) SetRootCertificate(pemFilePath string) *Client {
+ rootPemData, err := ioutil.ReadFile(pemFilePath)
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+
+ config, err := c.tlsConfig()
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+ if config.RootCAs == nil {
+ config.RootCAs = x509.NewCertPool()
+ }
+
+ config.RootCAs.AppendCertsFromPEM(rootPemData)
+ return c
+}
+
+// SetRootCertificateFromString method helps to add one or more root certificates into Resty client
+// client.SetRootCertificateFromString("pem file content")
+func (c *Client) SetRootCertificateFromString(pemContent string) *Client {
+ config, err := c.tlsConfig()
+ if err != nil {
+ c.log.Errorf("%v", err)
+ return c
+ }
+ if config.RootCAs == nil {
+ config.RootCAs = x509.NewCertPool()
+ }
+
+ config.RootCAs.AppendCertsFromPEM([]byte(pemContent))
+ return c
+}
+
+// SetOutputDirectory method sets output directory for saving HTTP response into file.
+// If the output directory not exists then resty creates one. This setting is optional one,
+// if you're planning using absolute path in `Request.SetOutput` and can used together.
+// client.SetOutputDirectory("/save/http/response/here")
+func (c *Client) SetOutputDirectory(dirPath string) *Client {
+ c.outputDirectory = dirPath
+ return c
+}
+
+// SetTransport method sets custom `*http.Transport` or any `http.RoundTripper`
+// compatible interface implementation in the resty client.
+//
+// Note:
+//
+// - If transport is not type of `*http.Transport` then you may not be able to
+// take advantage of some of the Resty client settings.
+//
+// - It overwrites the Resty client transport instance and it's configurations.
+//
+// transport := &http.Transport{
+// // somthing like Proxying to httptest.Server, etc...
+// Proxy: func(req *http.Request) (*url.URL, error) {
+// return url.Parse(server.URL)
+// },
+// }
+//
+// client.SetTransport(transport)
+func (c *Client) SetTransport(transport http.RoundTripper) *Client {
+ if transport != nil {
+ c.httpClient.Transport = transport
+ }
+ return c
+}
+
+// SetScheme method sets custom scheme in the Resty client. It's way to override default.
+// client.SetScheme("http")
+func (c *Client) SetScheme(scheme string) *Client {
+ if !IsStringEmpty(scheme) {
+ c.scheme = strings.TrimSpace(scheme)
+ }
+ return c
+}
+
+// SetCloseConnection method sets variable `Close` in http request struct with the given
+// value. More info: https://golang.org/src/net/http/request.go
+func (c *Client) SetCloseConnection(close bool) *Client {
+ c.closeConnection = close
+ return c
+}
+
+// SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
+// Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
+// otherwise you might get into connection leaks, no connection reuse.
+//
+// Note: Response middlewares are not applicable, if you use this option. Basically you have
+// taken over the control of response parsing from `Resty`.
+func (c *Client) SetDoNotParseResponse(parse bool) *Client {
+ c.notParseResponse = parse
+ return c
+}
+
+// SetPathParam method sets single URL path key-value pair in the
+// Resty client instance.
+// client.SetPathParam("userId", "sample@sample.com")
+//
+// Result:
+// URL - /v1/users/{userId}/details
+// Composed URL - /v1/users/sample@sample.com/details
+// It replaces the value of the key while composing the request URL.
+//
+// Also it can be overridden at request level Path Params options,
+// see `Request.SetPathParam` or `Request.SetPathParams`.
+func (c *Client) SetPathParam(param, value string) *Client {
+ c.PathParams[param] = value
+ return c
+}
+
+// SetPathParams method sets multiple URL path key-value pairs at one go in the
+// Resty client instance.
+// client.SetPathParams(map[string]string{
+// "userId": "sample@sample.com",
+// "subAccountId": "100002",
+// })
+//
+// Result:
+// URL - /v1/users/{userId}/{subAccountId}/details
+// Composed URL - /v1/users/sample@sample.com/100002/details
+// It replaces the value of the key while composing the request URL.
+//
+// Also it can be overridden at request level Path Params options,
+// see `Request.SetPathParam` or `Request.SetPathParams`.
+func (c *Client) SetPathParams(params map[string]string) *Client {
+ for p, v := range params {
+ c.SetPathParam(p, v)
+ }
+ return c
+}
+
+// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
+//
+// Note: This option only applicable to standard JSON Marshaller.
+func (c *Client) SetJSONEscapeHTML(b bool) *Client {
+ c.jsonEscapeHTML = b
+ return c
+}
+
+// EnableTrace method enables the Resty client trace for the requests fired from
+// the client using `httptrace.ClientTrace` and provides insights.
+//
+// client := resty.New().EnableTrace()
+//
+// resp, err := client.R().Get("https://httpbin.org/get")
+// fmt.Println("Error:", err)
+// fmt.Println("Trace Info:", resp.Request.TraceInfo())
+//
+// Also `Request.EnableTrace` available too to get trace info for single request.
+//
+// Since v2.0.0
+func (c *Client) EnableTrace() *Client {
+ c.trace = true
+ return c
+}
+
+// DisableTrace method disables the Resty client trace. Refer to `Client.EnableTrace`.
+//
+// Since v2.0.0
+func (c *Client) DisableTrace() *Client {
+ c.trace = false
+ return c
+}
+
+// IsProxySet method returns the true is proxy is set from resty client otherwise
+// false. By default proxy is set from environment, refer to `http.ProxyFromEnvironment`.
+func (c *Client) IsProxySet() bool {
+ return c.proxyURL != nil
+}
+
+// GetClient method returns the current `http.Client` used by the resty client.
+func (c *Client) GetClient() *http.Client {
+ return c.httpClient
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Client Unexported methods
+//_______________________________________________________________________
+
+// Executes method executes the given `Request` object and returns response
+// error.
+func (c *Client) execute(req *Request) (*Response, error) {
+ // Apply Request middleware
+ var err error
+
+ // user defined on before request methods
+ // to modify the *resty.Request object
+ for _, f := range c.udBeforeRequest {
+ if err = f(c, req); err != nil {
+ return nil, wrapNoRetryErr(err)
+ }
+ }
+
+ // resty middlewares
+ for _, f := range c.beforeRequest {
+ if err = f(c, req); err != nil {
+ return nil, wrapNoRetryErr(err)
+ }
+ }
+
+ if hostHeader := req.Header.Get("Host"); hostHeader != "" {
+ req.RawRequest.Host = hostHeader
+ }
+
+ // call pre-request if defined
+ if c.preReqHook != nil {
+ if err = c.preReqHook(c, req.RawRequest); err != nil {
+ return nil, wrapNoRetryErr(err)
+ }
+ }
+
+ if err = requestLogger(c, req); err != nil {
+ return nil, wrapNoRetryErr(err)
+ }
+
+ req.RawRequest.Body = newRequestBodyReleaser(req.RawRequest.Body, req.bodyBuf)
+
+ req.Time = time.Now()
+ resp, err := c.httpClient.Do(req.RawRequest)
+
+ response := &Response{
+ Request: req,
+ RawResponse: resp,
+ }
+
+ if err != nil || req.notParseResponse || c.notParseResponse {
+ response.setReceivedAt()
+ return response, err
+ }
+
+ if !req.isSaveResponse {
+ defer closeq(resp.Body)
+ body := resp.Body
+
+ // GitHub #142 & #187
+ if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.ContentLength != 0 {
+ if _, ok := body.(*gzip.Reader); !ok {
+ body, err = gzip.NewReader(body)
+ if err != nil {
+ response.setReceivedAt()
+ return response, err
+ }
+ defer closeq(body)
+ }
+ }
+
+ if response.body, err = ioutil.ReadAll(body); err != nil {
+ response.setReceivedAt()
+ return response, err
+ }
+
+ response.size = int64(len(response.body))
+ }
+
+ response.setReceivedAt() // after we read the body
+
+ // Apply Response middleware
+ for _, f := range c.afterResponse {
+ if err = f(c, response); err != nil {
+ break
+ }
+ }
+
+ return response, wrapNoRetryErr(err)
+}
+
+// getting TLS client config if not exists then create one
+func (c *Client) tlsConfig() (*tls.Config, error) {
+ transport, err := c.transport()
+ if err != nil {
+ return nil, err
+ }
+ if transport.TLSClientConfig == nil {
+ transport.TLSClientConfig = &tls.Config{}
+ }
+ return transport.TLSClientConfig, nil
+}
+
+// Transport method returns `*http.Transport` currently in use or error
+// in case currently used `transport` is not a `*http.Transport`.
+func (c *Client) transport() (*http.Transport, error) {
+ if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
+ return transport, nil
+ }
+ return nil, errors.New("current transport is not an *http.Transport instance")
+}
+
+// just an internal helper method
+func (c *Client) outputLogTo(w io.Writer) *Client {
+ c.log.(*logger).l.SetOutput(w)
+ return c
+}
+
+// ResponseError is a wrapper for including the server response with an error.
+// Neither the err nor the response should be nil.
+type ResponseError struct {
+ Response *Response
+ Err error
+}
+
+func (e *ResponseError) Error() string {
+ return e.Err.Error()
+}
+
+func (e *ResponseError) Unwrap() error {
+ return e.Err
+}
+
+// Helper to run onErrorHooks hooks.
+// It wraps the error in a ResponseError if the resp is not nil
+// so hooks can access it.
+func (c *Client) onErrorHooks(req *Request, resp *Response, err error) {
+ if err != nil {
+ if resp != nil { // wrap with ResponseError
+ err = &ResponseError{Response: resp, Err: err}
+ }
+ for _, h := range c.errorHooks {
+ h(req, err)
+ }
+ }
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// File struct and its methods
+//_______________________________________________________________________
+
+// File struct represent file information for multipart request
+type File struct {
+ Name string
+ ParamName string
+ io.Reader
+}
+
+// String returns string value of current file details
+func (f *File) String() string {
+ return fmt.Sprintf("ParamName: %v; FileName: %v", f.ParamName, f.Name)
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// MultipartField struct
+//_______________________________________________________________________
+
+// MultipartField struct represent custom data part for multipart request
+type MultipartField struct {
+ Param string
+ FileName string
+ ContentType string
+ io.Reader
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Unexported package methods
+//_______________________________________________________________________
+
+func createClient(hc *http.Client) *Client {
+ if hc.Transport == nil {
+ hc.Transport = createTransport(nil)
+ }
+
+ c := &Client{ // not setting lang default values
+ QueryParam: url.Values{},
+ FormData: url.Values{},
+ Header: http.Header{},
+ Cookies: make([]*http.Cookie, 0),
+ RetryWaitTime: defaultWaitTime,
+ RetryMaxWaitTime: defaultMaxWaitTime,
+ PathParams: make(map[string]string),
+ JSONMarshal: json.Marshal,
+ JSONUnmarshal: json.Unmarshal,
+ XMLMarshal: xml.Marshal,
+ XMLUnmarshal: xml.Unmarshal,
+ HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"),
+
+ jsonEscapeHTML: true,
+ httpClient: hc,
+ debugBodySizeLimit: math.MaxInt32,
+ }
+
+ // Logger
+ c.SetLogger(createLogger())
+
+ // default before request middlewares
+ c.beforeRequest = []RequestMiddleware{
+ parseRequestURL,
+ parseRequestHeader,
+ parseRequestBody,
+ createHTTPRequest,
+ addCredentials,
+ }
+
+ // user defined request middlewares
+ c.udBeforeRequest = []RequestMiddleware{}
+
+ // default after response middlewares
+ c.afterResponse = []ResponseMiddleware{
+ responseLogger,
+ parseResponseBody,
+ saveResponseIntoFile,
+ }
+
+ return c
+}
diff --git a/vendor/github.com/go-resty/resty/v2/middleware.go b/vendor/github.com/go-resty/resty/v2/middleware.go
new file mode 100644
index 000000000..0e8ac2b69
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/middleware.go
@@ -0,0 +1,543 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "time"
+)
+
+const debugRequestLogKey = "__restyDebugRequestLog"
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Request Middleware(s)
+//_______________________________________________________________________
+
+func parseRequestURL(c *Client, r *Request) error {
+ // GitHub #103 Path Params
+ if len(r.PathParams) > 0 {
+ for p, v := range r.PathParams {
+ r.URL = strings.Replace(r.URL, "{"+p+"}", url.PathEscape(v), -1)
+ }
+ }
+ if len(c.PathParams) > 0 {
+ for p, v := range c.PathParams {
+ r.URL = strings.Replace(r.URL, "{"+p+"}", url.PathEscape(v), -1)
+ }
+ }
+
+ // Parsing request URL
+ reqURL, err := url.Parse(r.URL)
+ if err != nil {
+ return err
+ }
+
+ // If Request.URL is relative path then added c.HostURL into
+ // the request URL otherwise Request.URL will be used as-is
+ if !reqURL.IsAbs() {
+ r.URL = reqURL.String()
+ if len(r.URL) > 0 && r.URL[0] != '/' {
+ r.URL = "/" + r.URL
+ }
+
+ reqURL, err = url.Parse(c.HostURL + r.URL)
+ if err != nil {
+ return err
+ }
+ }
+
+ // GH #407 && #318
+ if reqURL.Scheme == "" && len(c.scheme) > 0 {
+ reqURL.Scheme = c.scheme
+ }
+
+ // Adding Query Param
+ query := make(url.Values)
+ for k, v := range c.QueryParam {
+ for _, iv := range v {
+ query.Add(k, iv)
+ }
+ }
+
+ for k, v := range r.QueryParam {
+ // remove query param from client level by key
+ // since overrides happens for that key in the request
+ query.Del(k)
+
+ for _, iv := range v {
+ query.Add(k, iv)
+ }
+ }
+
+ // GitHub #123 Preserve query string order partially.
+ // Since not feasible in `SetQuery*` resty methods, because
+ // standard package `url.Encode(...)` sorts the query params
+ // alphabetically
+ if len(query) > 0 {
+ if IsStringEmpty(reqURL.RawQuery) {
+ reqURL.RawQuery = query.Encode()
+ } else {
+ reqURL.RawQuery = reqURL.RawQuery + "&" + query.Encode()
+ }
+ }
+
+ r.URL = reqURL.String()
+
+ return nil
+}
+
+func parseRequestHeader(c *Client, r *Request) error {
+ hdr := make(http.Header)
+ for k := range c.Header {
+ hdr[k] = append(hdr[k], c.Header[k]...)
+ }
+
+ for k := range r.Header {
+ hdr.Del(k)
+ hdr[k] = append(hdr[k], r.Header[k]...)
+ }
+
+ if IsStringEmpty(hdr.Get(hdrUserAgentKey)) {
+ hdr.Set(hdrUserAgentKey, hdrUserAgentValue)
+ }
+
+ ct := hdr.Get(hdrContentTypeKey)
+ if IsStringEmpty(hdr.Get(hdrAcceptKey)) && !IsStringEmpty(ct) &&
+ (IsJSONType(ct) || IsXMLType(ct)) {
+ hdr.Set(hdrAcceptKey, hdr.Get(hdrContentTypeKey))
+ }
+
+ r.Header = hdr
+
+ return nil
+}
+
+func parseRequestBody(c *Client, r *Request) (err error) {
+ if isPayloadSupported(r.Method, c.AllowGetMethodPayload) {
+ // Handling Multipart
+ if r.isMultiPart && !(r.Method == MethodPatch) {
+ if err = handleMultipart(c, r); err != nil {
+ return
+ }
+
+ goto CL
+ }
+
+ // Handling Form Data
+ if len(c.FormData) > 0 || len(r.FormData) > 0 {
+ handleFormData(c, r)
+
+ goto CL
+ }
+
+ // Handling Request body
+ if r.Body != nil {
+ handleContentType(c, r)
+
+ if err = handleRequestBody(c, r); err != nil {
+ return
+ }
+ }
+ }
+
+CL:
+ // by default resty won't set content length, you can if you want to :)
+ if (c.setContentLength || r.setContentLength) && r.bodyBuf != nil {
+ r.Header.Set(hdrContentLengthKey, fmt.Sprintf("%d", r.bodyBuf.Len()))
+ }
+
+ return
+}
+
+func createHTTPRequest(c *Client, r *Request) (err error) {
+ if r.bodyBuf == nil {
+ if reader, ok := r.Body.(io.Reader); ok {
+ r.RawRequest, err = http.NewRequest(r.Method, r.URL, reader)
+ } else if c.setContentLength || r.setContentLength {
+ r.RawRequest, err = http.NewRequest(r.Method, r.URL, http.NoBody)
+ } else {
+ r.RawRequest, err = http.NewRequest(r.Method, r.URL, nil)
+ }
+ } else {
+ r.RawRequest, err = http.NewRequest(r.Method, r.URL, r.bodyBuf)
+ }
+
+ if err != nil {
+ return
+ }
+
+ // Assign close connection option
+ r.RawRequest.Close = c.closeConnection
+
+ // Add headers into http request
+ r.RawRequest.Header = r.Header
+
+ // Add cookies from client instance into http request
+ for _, cookie := range c.Cookies {
+ r.RawRequest.AddCookie(cookie)
+ }
+
+ // Add cookies from request instance into http request
+ for _, cookie := range r.Cookies {
+ r.RawRequest.AddCookie(cookie)
+ }
+
+ // Enable trace
+ if c.trace || r.trace {
+ r.clientTrace = &clientTrace{}
+ r.ctx = r.clientTrace.createContext(r.Context())
+ }
+
+ // Use context if it was specified
+ if r.ctx != nil {
+ r.RawRequest = r.RawRequest.WithContext(r.ctx)
+ }
+
+ bodyCopy, err := getBodyCopy(r)
+ if err != nil {
+ return err
+ }
+
+ // assign get body func for the underlying raw request instance
+ r.RawRequest.GetBody = func() (io.ReadCloser, error) {
+ if bodyCopy != nil {
+ return ioutil.NopCloser(bytes.NewReader(bodyCopy.Bytes())), nil
+ }
+ return nil, nil
+ }
+
+ return
+}
+
+func addCredentials(c *Client, r *Request) error {
+ var isBasicAuth bool
+ // Basic Auth
+ if r.UserInfo != nil { // takes precedence
+ r.RawRequest.SetBasicAuth(r.UserInfo.Username, r.UserInfo.Password)
+ isBasicAuth = true
+ } else if c.UserInfo != nil {
+ r.RawRequest.SetBasicAuth(c.UserInfo.Username, c.UserInfo.Password)
+ isBasicAuth = true
+ }
+
+ if !c.DisableWarn {
+ if isBasicAuth && !strings.HasPrefix(r.URL, "https") {
+ c.log.Warnf("Using Basic Auth in HTTP mode is not secure, use HTTPS")
+ }
+ }
+
+ // Set the Authorization Header Scheme
+ var authScheme string
+ if !IsStringEmpty(r.AuthScheme) {
+ authScheme = r.AuthScheme
+ } else if !IsStringEmpty(c.AuthScheme) {
+ authScheme = c.AuthScheme
+ } else {
+ authScheme = "Bearer"
+ }
+
+ // Build the Token Auth header
+ if !IsStringEmpty(r.Token) { // takes precedence
+ r.RawRequest.Header.Set(c.HeaderAuthorizationKey, authScheme+" "+r.Token)
+ } else if !IsStringEmpty(c.Token) {
+ r.RawRequest.Header.Set(c.HeaderAuthorizationKey, authScheme+" "+c.Token)
+ }
+
+ return nil
+}
+
+func requestLogger(c *Client, r *Request) error {
+ if c.Debug {
+ rr := r.RawRequest
+ rl := &RequestLog{Header: copyHeaders(rr.Header), Body: r.fmtBodyString(c.debugBodySizeLimit)}
+ if c.requestLog != nil {
+ if err := c.requestLog(rl); err != nil {
+ return err
+ }
+ }
+ // fmt.Sprintf("COOKIES:\n%s\n", composeCookies(c.GetClient().Jar, *rr.URL)) +
+
+ reqLog := "\n==============================================================================\n" +
+ "~~~ REQUEST ~~~\n" +
+ fmt.Sprintf("%s %s %s\n", r.Method, rr.URL.RequestURI(), rr.Proto) +
+ fmt.Sprintf("HOST : %s\n", rr.URL.Host) +
+ fmt.Sprintf("HEADERS:\n%s\n", composeHeaders(c, r, rl.Header)) +
+ fmt.Sprintf("BODY :\n%v\n", rl.Body) +
+ "------------------------------------------------------------------------------\n"
+
+ r.initValuesMap()
+ r.values[debugRequestLogKey] = reqLog
+ }
+
+ return nil
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Response Middleware(s)
+//_______________________________________________________________________
+
+func responseLogger(c *Client, res *Response) error {
+ if c.Debug {
+ rl := &ResponseLog{Header: copyHeaders(res.Header()), Body: res.fmtBodyString(c.debugBodySizeLimit)}
+ if c.responseLog != nil {
+ if err := c.responseLog(rl); err != nil {
+ return err
+ }
+ }
+
+ debugLog := res.Request.values[debugRequestLogKey].(string)
+ debugLog += "~~~ RESPONSE ~~~\n" +
+ fmt.Sprintf("STATUS : %s\n", res.Status()) +
+ fmt.Sprintf("PROTO : %s\n", res.RawResponse.Proto) +
+ fmt.Sprintf("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) +
+ fmt.Sprintf("TIME DURATION: %v\n", res.Time()) +
+ "HEADERS :\n" +
+ composeHeaders(c, res.Request, rl.Header) + "\n"
+ if res.Request.isSaveResponse {
+ debugLog += "BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n"
+ } else {
+ debugLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
+ }
+ debugLog += "==============================================================================\n"
+
+ c.log.Debugf("%s", debugLog)
+ }
+
+ return nil
+}
+
+func parseResponseBody(c *Client, res *Response) (err error) {
+ if res.StatusCode() == http.StatusNoContent {
+ return
+ }
+ // Handles only JSON or XML content type
+ ct := firstNonEmpty(res.Request.forceContentType, res.Header().Get(hdrContentTypeKey), res.Request.fallbackContentType)
+ if IsJSONType(ct) || IsXMLType(ct) {
+ // HTTP status code > 199 and < 300, considered as Result
+ if res.IsSuccess() {
+ res.Request.Error = nil
+ if res.Request.Result != nil {
+ err = Unmarshalc(c, ct, res.body, res.Request.Result)
+ return
+ }
+ }
+
+ // HTTP status code > 399, considered as Error
+ if res.IsError() {
+ // global error interface
+ if res.Request.Error == nil && c.Error != nil {
+ res.Request.Error = reflect.New(c.Error).Interface()
+ }
+
+ if res.Request.Error != nil {
+ err = Unmarshalc(c, ct, res.body, res.Request.Error)
+ }
+ }
+ }
+
+ return
+}
+
+func handleMultipart(c *Client, r *Request) (err error) {
+ r.bodyBuf = acquireBuffer()
+ w := multipart.NewWriter(r.bodyBuf)
+
+ for k, v := range c.FormData {
+ for _, iv := range v {
+ if err = w.WriteField(k, iv); err != nil {
+ return err
+ }
+ }
+ }
+
+ for k, v := range r.FormData {
+ for _, iv := range v {
+ if strings.HasPrefix(k, "@") { // file
+ err = addFile(w, k[1:], iv)
+ if err != nil {
+ return
+ }
+ } else { // form value
+ if err = w.WriteField(k, iv); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ // #21 - adding io.Reader support
+ if len(r.multipartFiles) > 0 {
+ for _, f := range r.multipartFiles {
+ err = addFileReader(w, f)
+ if err != nil {
+ return
+ }
+ }
+ }
+
+ // GitHub #130 adding multipart field support with content type
+ if len(r.multipartFields) > 0 {
+ for _, mf := range r.multipartFields {
+ if err = addMultipartFormField(w, mf); err != nil {
+ return
+ }
+ }
+ }
+
+ r.Header.Set(hdrContentTypeKey, w.FormDataContentType())
+ err = w.Close()
+
+ return
+}
+
+func handleFormData(c *Client, r *Request) {
+ formData := url.Values{}
+
+ for k, v := range c.FormData {
+ for _, iv := range v {
+ formData.Add(k, iv)
+ }
+ }
+
+ for k, v := range r.FormData {
+ // remove form data field from client level by key
+ // since overrides happens for that key in the request
+ formData.Del(k)
+
+ for _, iv := range v {
+ formData.Add(k, iv)
+ }
+ }
+
+ r.bodyBuf = bytes.NewBuffer([]byte(formData.Encode()))
+ r.Header.Set(hdrContentTypeKey, formContentType)
+ r.isFormData = true
+}
+
+func handleContentType(c *Client, r *Request) {
+ contentType := r.Header.Get(hdrContentTypeKey)
+ if IsStringEmpty(contentType) {
+ contentType = DetectContentType(r.Body)
+ r.Header.Set(hdrContentTypeKey, contentType)
+ }
+}
+
+func handleRequestBody(c *Client, r *Request) (err error) {
+ var bodyBytes []byte
+ contentType := r.Header.Get(hdrContentTypeKey)
+ kind := kindOf(r.Body)
+ r.bodyBuf = nil
+
+ if reader, ok := r.Body.(io.Reader); ok {
+ if c.setContentLength || r.setContentLength { // keep backward compatibility
+ r.bodyBuf = acquireBuffer()
+ _, err = r.bodyBuf.ReadFrom(reader)
+ r.Body = nil
+ } else {
+ // Otherwise buffer less processing for `io.Reader`, sounds good.
+ return
+ }
+ } else if b, ok := r.Body.([]byte); ok {
+ bodyBytes = b
+ } else if s, ok := r.Body.(string); ok {
+ bodyBytes = []byte(s)
+ } else if IsJSONType(contentType) &&
+ (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
+ r.bodyBuf, err = jsonMarshal(c, r, r.Body)
+ if err != nil {
+ return
+ }
+ } else if IsXMLType(contentType) && (kind == reflect.Struct) {
+ bodyBytes, err = c.XMLMarshal(r.Body)
+ if err != nil {
+ return
+ }
+ }
+
+ if bodyBytes == nil && r.bodyBuf == nil {
+ err = errors.New("unsupported 'Body' type/value")
+ }
+
+ // if any errors during body bytes handling, return it
+ if err != nil {
+ return
+ }
+
+ // []byte into Buffer
+ if bodyBytes != nil && r.bodyBuf == nil {
+ r.bodyBuf = acquireBuffer()
+ _, _ = r.bodyBuf.Write(bodyBytes)
+ }
+
+ return
+}
+
+func saveResponseIntoFile(c *Client, res *Response) error {
+ if res.Request.isSaveResponse {
+ file := ""
+
+ if len(c.outputDirectory) > 0 && !filepath.IsAbs(res.Request.outputFile) {
+ file += c.outputDirectory + string(filepath.Separator)
+ }
+
+ file = filepath.Clean(file + res.Request.outputFile)
+ if err := createDirectory(filepath.Dir(file)); err != nil {
+ return err
+ }
+
+ outFile, err := os.Create(file)
+ if err != nil {
+ return err
+ }
+ defer closeq(outFile)
+
+ // io.Copy reads maximum 32kb size, it is perfect for large file download too
+ defer closeq(res.RawResponse.Body)
+
+ written, err := io.Copy(outFile, res.RawResponse.Body)
+ if err != nil {
+ return err
+ }
+
+ res.size = written
+ }
+
+ return nil
+}
+
+func getBodyCopy(r *Request) (*bytes.Buffer, error) {
+ // If r.bodyBuf present, return the copy
+ if r.bodyBuf != nil {
+ return bytes.NewBuffer(r.bodyBuf.Bytes()), nil
+ }
+
+ // Maybe body is `io.Reader`.
+ // Note: Resty user have to watchout for large body size of `io.Reader`
+ if r.RawRequest.Body != nil {
+ b, err := ioutil.ReadAll(r.RawRequest.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Restore the Body
+ closeq(r.RawRequest.Body)
+ r.RawRequest.Body = ioutil.NopCloser(bytes.NewBuffer(b))
+
+ // Return the Body bytes
+ return bytes.NewBuffer(b), nil
+ }
+ return nil, nil
+}
diff --git a/vendor/github.com/go-resty/resty/v2/redirect.go b/vendor/github.com/go-resty/resty/v2/redirect.go
new file mode 100644
index 000000000..7d7e43bc1
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/redirect.go
@@ -0,0 +1,101 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "net/http"
+ "strings"
+)
+
+type (
+ // RedirectPolicy to regulate the redirects in the resty client.
+ // Objects implementing the RedirectPolicy interface can be registered as
+ //
+ // Apply function should return nil to continue the redirect jounery, otherwise
+ // return error to stop the redirect.
+ RedirectPolicy interface {
+ Apply(req *http.Request, via []*http.Request) error
+ }
+
+ // The RedirectPolicyFunc type is an adapter to allow the use of ordinary functions as RedirectPolicy.
+ // If f is a function with the appropriate signature, RedirectPolicyFunc(f) is a RedirectPolicy object that calls f.
+ RedirectPolicyFunc func(*http.Request, []*http.Request) error
+)
+
+// Apply calls f(req, via).
+func (f RedirectPolicyFunc) Apply(req *http.Request, via []*http.Request) error {
+ return f(req, via)
+}
+
+// NoRedirectPolicy is used to disable redirects in the HTTP client
+// resty.SetRedirectPolicy(NoRedirectPolicy())
+func NoRedirectPolicy() RedirectPolicy {
+ return RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
+ return errors.New("auto redirect is disabled")
+ })
+}
+
+// FlexibleRedirectPolicy is convenient method to create No of redirect policy for HTTP client.
+// resty.SetRedirectPolicy(FlexibleRedirectPolicy(20))
+func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy {
+ return RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
+ if len(via) >= noOfRedirect {
+ return fmt.Errorf("stopped after %d redirects", noOfRedirect)
+ }
+ checkHostAndAddHeaders(req, via[0])
+ return nil
+ })
+}
+
+// DomainCheckRedirectPolicy is convenient method to define domain name redirect rule in resty client.
+// Redirect is allowed for only mentioned host in the policy.
+// resty.SetRedirectPolicy(DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
+func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy {
+ hosts := make(map[string]bool)
+ for _, h := range hostnames {
+ hosts[strings.ToLower(h)] = true
+ }
+
+ fn := RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
+ if ok := hosts[getHostname(req.URL.Host)]; !ok {
+ return errors.New("redirect is not allowed as per DomainCheckRedirectPolicy")
+ }
+
+ return nil
+ })
+
+ return fn
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Package Unexported methods
+//_______________________________________________________________________
+
+func getHostname(host string) (hostname string) {
+ if strings.Index(host, ":") > 0 {
+ host, _, _ = net.SplitHostPort(host)
+ }
+ hostname = strings.ToLower(host)
+ return
+}
+
+// By default Golang will not redirect request headers
+// after go throughing various discussion comments from thread
+// https://github.com/golang/go/issues/4800
+// Resty will add all the headers during a redirect for the same host
+func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) {
+ curHostname := getHostname(cur.URL.Host)
+ preHostname := getHostname(pre.URL.Host)
+ if strings.EqualFold(curHostname, preHostname) {
+ for key, val := range pre.Header {
+ cur.Header[key] = val
+ }
+ } else { // only library User-Agent header is added
+ cur.Header.Set(hdrUserAgentKey, hdrUserAgentValue)
+ }
+}
diff --git a/vendor/github.com/go-resty/resty/v2/request.go b/vendor/github.com/go-resty/resty/v2/request.go
new file mode 100644
index 000000000..672df88c3
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/request.go
@@ -0,0 +1,896 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "reflect"
+ "strings"
+ "time"
+)
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Request struct and methods
+//_______________________________________________________________________
+
+// Request struct is used to compose and fire individual request from
+// resty client. Request provides an options to override client level
+// settings and also an options for the request composition.
+type Request struct {
+ URL string
+ Method string
+ Token string
+ AuthScheme string
+ QueryParam url.Values
+ FormData url.Values
+ PathParams map[string]string
+ Header http.Header
+ Time time.Time
+ Body interface{}
+ Result interface{}
+ Error interface{}
+ RawRequest *http.Request
+ SRV *SRVRecord
+ UserInfo *User
+ Cookies []*http.Cookie
+
+ // Attempt is to represent the request attempt made during a Resty
+ // request execution flow, including retry count.
+ //
+ // Since v2.4.0
+ Attempt int
+
+ isMultiPart bool
+ isFormData bool
+ setContentLength bool
+ isSaveResponse bool
+ notParseResponse bool
+ jsonEscapeHTML bool
+ trace bool
+ outputFile string
+ fallbackContentType string
+ forceContentType string
+ ctx context.Context
+ values map[string]interface{}
+ client *Client
+ bodyBuf *bytes.Buffer
+ clientTrace *clientTrace
+ multipartFiles []*File
+ multipartFields []*MultipartField
+ retryConditions []RetryConditionFunc
+}
+
+// Context method returns the Context if its already set in request
+// otherwise it creates new one using `context.Background()`.
+func (r *Request) Context() context.Context {
+ if r.ctx == nil {
+ return context.Background()
+ }
+ return r.ctx
+}
+
+// SetContext method sets the context.Context for current Request. It allows
+// to interrupt the request execution if ctx.Done() channel is closed.
+// See https://blog.golang.org/context article and the "context" package
+// documentation.
+func (r *Request) SetContext(ctx context.Context) *Request {
+ r.ctx = ctx
+ return r
+}
+
+// SetHeader method is to set a single header field and its value in the current request.
+//
+// For Example: To set `Content-Type` and `Accept` as `application/json`.
+// client.R().
+// SetHeader("Content-Type", "application/json").
+// SetHeader("Accept", "application/json")
+//
+// Also you can override header value, which was set at client instance level.
+func (r *Request) SetHeader(header, value string) *Request {
+ r.Header.Set(header, value)
+ return r
+}
+
+// SetHeaders method sets multiple headers field and its values at one go in the current request.
+//
+// For Example: To set `Content-Type` and `Accept` as `application/json`
+//
+// client.R().
+// SetHeaders(map[string]string{
+// "Content-Type": "application/json",
+// "Accept": "application/json",
+// })
+// Also you can override header value, which was set at client instance level.
+func (r *Request) SetHeaders(headers map[string]string) *Request {
+ for h, v := range headers {
+ r.SetHeader(h, v)
+ }
+ return r
+}
+
+// SetHeaderMultiValues sets multiple headers fields and its values is list of strings at one go in the current request.
+//
+// For Example: To set `Accept` as `text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8`
+//
+// client.R().
+// SetHeaderMultiValues(map[string][]string{
+// "Accept": []string{"text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"},
+// })
+// Also you can override header value, which was set at client instance level.
+func (r *Request) SetHeaderMultiValues(headers map[string][]string) *Request {
+ for key, values := range headers {
+ r.SetHeader(key, strings.Join(values, ", "))
+ }
+ return r
+}
+
+// SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.
+//
+// For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
+// client.R().
+// SetHeaderVerbatim("all_lowercase", "available").
+// SetHeaderVerbatim("UPPERCASE", "available")
+//
+// Also you can override header value, which was set at client instance level.
+//
+// Since v2.6.0
+func (r *Request) SetHeaderVerbatim(header, value string) *Request {
+ r.Header[header] = []string{value}
+ return r
+}
+
+// SetQueryParam method sets single parameter and its value in the current request.
+// It will be formed as query string for the request.
+//
+// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
+// client.R().
+// SetQueryParam("search", "kitchen papers").
+// SetQueryParam("size", "large")
+// Also you can override query params value, which was set at client instance level.
+func (r *Request) SetQueryParam(param, value string) *Request {
+ r.QueryParam.Set(param, value)
+ return r
+}
+
+// SetQueryParams method sets multiple parameters and its values at one go in the current request.
+// It will be formed as query string for the request.
+//
+// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
+// client.R().
+// SetQueryParams(map[string]string{
+// "search": "kitchen papers",
+// "size": "large",
+// })
+// Also you can override query params value, which was set at client instance level.
+func (r *Request) SetQueryParams(params map[string]string) *Request {
+ for p, v := range params {
+ r.SetQueryParam(p, v)
+ }
+ return r
+}
+
+// SetQueryParamsFromValues method appends multiple parameters with multi-value
+// (`url.Values`) at one go in the current request. It will be formed as
+// query string for the request.
+//
+// For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
+// client.R().
+// SetQueryParamsFromValues(url.Values{
+// "status": []string{"pending", "approved", "open"},
+// })
+// Also you can override query params value, which was set at client instance level.
+func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
+ for p, v := range params {
+ for _, pv := range v {
+ r.QueryParam.Add(p, pv)
+ }
+ }
+ return r
+}
+
+// SetQueryString method provides ability to use string as an input to set URL query string for the request.
+//
+// Using String as an input
+// client.R().
+// SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
+func (r *Request) SetQueryString(query string) *Request {
+ params, err := url.ParseQuery(strings.TrimSpace(query))
+ if err == nil {
+ for p, v := range params {
+ for _, pv := range v {
+ r.QueryParam.Add(p, pv)
+ }
+ }
+ } else {
+ r.client.log.Errorf("%v", err)
+ }
+ return r
+}
+
+// SetFormData method sets Form parameters and their values in the current request.
+// It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as
+// `application/x-www-form-urlencoded`.
+// client.R().
+// SetFormData(map[string]string{
+// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
+// "user_id": "3455454545",
+// })
+// Also you can override form data value, which was set at client instance level.
+func (r *Request) SetFormData(data map[string]string) *Request {
+ for k, v := range data {
+ r.FormData.Set(k, v)
+ }
+ return r
+}
+
+// SetFormDataFromValues method appends multiple form parameters with multi-value
+// (`url.Values`) at one go in the current request.
+// client.R().
+// SetFormDataFromValues(url.Values{
+// "search_criteria": []string{"book", "glass", "pencil"},
+// })
+// Also you can override form data value, which was set at client instance level.
+func (r *Request) SetFormDataFromValues(data url.Values) *Request {
+ for k, v := range data {
+ for _, kv := range v {
+ r.FormData.Add(k, kv)
+ }
+ }
+ return r
+}
+
+// SetBody method sets the request body for the request. It supports various realtime needs as easy.
+// We can say its quite handy or powerful. Supported request body data types is `string`,
+// `[]byte`, `struct`, `map`, `slice` and `io.Reader`. Body value can be pointer or non-pointer.
+// Automatic marshalling for JSON and XML content type, if it is `struct`, `map`, or `slice`.
+//
+// Note: `io.Reader` is processed as bufferless mode while sending request.
+//
+// For Example: Struct as a body input, based on content type, it will be marshalled.
+// client.R().
+// SetBody(User{
+// Username: "jeeva@myjeeva.com",
+// Password: "welcome2resty",
+// })
+//
+// Map as a body input, based on content type, it will be marshalled.
+// client.R().
+// SetBody(map[string]interface{}{
+// "username": "jeeva@myjeeva.com",
+// "password": "welcome2resty",
+// "address": &Address{
+// Address1: "1111 This is my street",
+// Address2: "Apt 201",
+// City: "My City",
+// State: "My State",
+// ZipCode: 00000,
+// },
+// })
+//
+// String as a body input. Suitable for any need as a string input.
+// client.R().
+// SetBody(`{
+// "username": "jeeva@getrightcare.com",
+// "password": "admin"
+// }`)
+//
+// []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.
+// client.R().
+// SetBody([]byte("This is my raw request, sent as-is"))
+func (r *Request) SetBody(body interface{}) *Request {
+ r.Body = body
+ return r
+}
+
+// SetResult method is to register the response `Result` object for automatic unmarshalling for the request,
+// if response status code is between 200 and 299 and content type either JSON or XML.
+//
+// Note: Result object can be pointer or non-pointer.
+// client.R().SetResult(&AuthToken{})
+// // OR
+// client.R().SetResult(AuthToken{})
+//
+// Accessing a result value from response instance.
+// response.Result().(*AuthToken)
+func (r *Request) SetResult(res interface{}) *Request {
+ r.Result = getPointer(res)
+ return r
+}
+
+// SetError method is to register the request `Error` object for automatic unmarshalling for the request,
+// if response status code is greater than 399 and content type either JSON or XML.
+//
+// Note: Error object can be pointer or non-pointer.
+// client.R().SetError(&AuthError{})
+// // OR
+// client.R().SetError(AuthError{})
+//
+// Accessing a error value from response instance.
+// response.Error().(*AuthError)
+func (r *Request) SetError(err interface{}) *Request {
+ r.Error = getPointer(err)
+ return r
+}
+
+// SetFile method is to set single file field name and its path for multipart upload.
+// client.R().
+// SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
+func (r *Request) SetFile(param, filePath string) *Request {
+ r.isMultiPart = true
+ r.FormData.Set("@"+param, filePath)
+ return r
+}
+
+// SetFiles method is to set multiple file field name and its path for multipart upload.
+// client.R().
+// SetFiles(map[string]string{
+// "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
+// "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
+// "my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
+// })
+func (r *Request) SetFiles(files map[string]string) *Request {
+ r.isMultiPart = true
+ for f, fp := range files {
+ r.FormData.Set("@"+f, fp)
+ }
+ return r
+}
+
+// SetFileReader method is to set single file using io.Reader for multipart upload.
+// client.R().
+// SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
+// SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
+func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
+ r.isMultiPart = true
+ r.multipartFiles = append(r.multipartFiles, &File{
+ Name: fileName,
+ ParamName: param,
+ Reader: reader,
+ })
+ return r
+}
+
+// SetMultipartFormData method allows simple form data to be attached to the request as `multipart:form-data`
+func (r *Request) SetMultipartFormData(data map[string]string) *Request {
+ for k, v := range data {
+ r = r.SetMultipartField(k, "", "", strings.NewReader(v))
+ }
+
+ return r
+}
+
+// SetMultipartField method is to set custom data using io.Reader for multipart upload.
+func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request {
+ r.isMultiPart = true
+ r.multipartFields = append(r.multipartFields, &MultipartField{
+ Param: param,
+ FileName: fileName,
+ ContentType: contentType,
+ Reader: reader,
+ })
+ return r
+}
+
+// SetMultipartFields method is to set multiple data fields using io.Reader for multipart upload.
+//
+// For Example:
+// client.R().SetMultipartFields(
+// &resty.MultipartField{
+// Param: "uploadManifest1",
+// FileName: "upload-file-1.json",
+// ContentType: "application/json",
+// Reader: strings.NewReader(`{"input": {"name": "Uploaded document 1", "_filename" : ["file1.txt"]}}`),
+// },
+// &resty.MultipartField{
+// Param: "uploadManifest2",
+// FileName: "upload-file-2.json",
+// ContentType: "application/json",
+// Reader: strings.NewReader(`{"input": {"name": "Uploaded document 2", "_filename" : ["file2.txt"]}}`),
+// })
+//
+// If you have slice already, then simply call-
+// client.R().SetMultipartFields(fields...)
+func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
+ r.isMultiPart = true
+ r.multipartFields = append(r.multipartFields, fields...)
+ return r
+}
+
+// SetContentLength method sets the HTTP header `Content-Length` value for current request.
+// By default Resty won't set `Content-Length`. Also you have an option to enable for every
+// request.
+//
+// See `Client.SetContentLength`
+// client.R().SetContentLength(true)
+func (r *Request) SetContentLength(l bool) *Request {
+ r.setContentLength = l
+ return r
+}
+
+// SetBasicAuth method sets the basic authentication header in the current HTTP request.
+//
+// For Example:
+// Authorization: Basic
+//
+// To set the header for username "go-resty" and password "welcome"
+// client.R().SetBasicAuth("go-resty", "welcome")
+//
+// This method overrides the credentials set by method `Client.SetBasicAuth`.
+func (r *Request) SetBasicAuth(username, password string) *Request {
+ r.UserInfo = &User{Username: username, Password: password}
+ return r
+}
+
+// SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:
+// Authorization: Bearer
+//
+// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
+//
+// client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
+//
+// This method overrides the Auth token set by method `Client.SetAuthToken`.
+func (r *Request) SetAuthToken(token string) *Request {
+ r.Token = token
+ return r
+}
+
+// SetAuthScheme method sets the auth token scheme type in the HTTP request. For Example:
+// Authorization:
+//
+// For Example: To set the scheme to use OAuth
+//
+// client.R().SetAuthScheme("OAuth")
+//
+// This auth header scheme gets added to all the request rasied from this client instance.
+// Also it can be overridden or set one at the request level is supported.
+//
+// Information about Auth schemes can be found in RFC7235 which is linked to below along with the page containing
+// the currently defined official authentication schemes:
+// https://tools.ietf.org/html/rfc7235
+// https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
+//
+// This method overrides the Authorization scheme set by method `Client.SetAuthScheme`.
+func (r *Request) SetAuthScheme(scheme string) *Request {
+ r.AuthScheme = scheme
+ return r
+}
+
+// SetOutput method sets the output file for current HTTP request. Current HTTP response will be
+// saved into given file. It is similar to `curl -o` flag. Absolute path or relative path can be used.
+// If is it relative path then output file goes under the output directory, as mentioned
+// in the `Client.SetOutputDirectory`.
+// client.R().
+// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
+// Get("http://bit.ly/1LouEKr")
+//
+// Note: In this scenario `Response.Body` might be nil.
+func (r *Request) SetOutput(file string) *Request {
+ r.outputFile = file
+ r.isSaveResponse = true
+ return r
+}
+
+// SetSRV method sets the details to query the service SRV record and execute the
+// request.
+// client.R().
+// SetSRV(SRVRecord{"web", "testservice.com"}).
+// Get("/get")
+func (r *Request) SetSRV(srv *SRVRecord) *Request {
+ r.SRV = srv
+ return r
+}
+
+// SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
+// Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
+// otherwise you might get into connection leaks, no connection reuse.
+//
+// Note: Response middlewares are not applicable, if you use this option. Basically you have
+// taken over the control of response parsing from `Resty`.
+func (r *Request) SetDoNotParseResponse(parse bool) *Request {
+ r.notParseResponse = parse
+ return r
+}
+
+// SetPathParam method sets single URL path key-value pair in the
+// Resty current request instance.
+// client.R().SetPathParam("userId", "sample@sample.com")
+//
+// Result:
+// URL - /v1/users/{userId}/details
+// Composed URL - /v1/users/sample@sample.com/details
+// It replaces the value of the key while composing the request URL. Also you can
+// override Path Params value, which was set at client instance level.
+func (r *Request) SetPathParam(param, value string) *Request {
+ r.PathParams[param] = value
+ return r
+}
+
+// SetPathParams method sets multiple URL path key-value pairs at one go in the
+// Resty current request instance.
+// client.R().SetPathParams(map[string]string{
+// "userId": "sample@sample.com",
+// "subAccountId": "100002",
+// })
+//
+// Result:
+// URL - /v1/users/{userId}/{subAccountId}/details
+// Composed URL - /v1/users/sample@sample.com/100002/details
+// It replaces the value of the key while composing request URL. Also you can
+// override Path Params value, which was set at client instance level.
+func (r *Request) SetPathParams(params map[string]string) *Request {
+ for p, v := range params {
+ r.SetPathParam(p, v)
+ }
+ return r
+}
+
+// ExpectContentType method allows to provide fallback `Content-Type` for automatic unmarshalling
+// when `Content-Type` response header is unavailable.
+func (r *Request) ExpectContentType(contentType string) *Request {
+ r.fallbackContentType = contentType
+ return r
+}
+
+// ForceContentType method provides a strong sense of response `Content-Type` for automatic unmarshalling.
+// Resty gives this a higher priority than the `Content-Type` response header. This means that if both
+// `Request.ForceContentType` is set and the response `Content-Type` is available, `ForceContentType` will win.
+func (r *Request) ForceContentType(contentType string) *Request {
+ r.forceContentType = contentType
+ return r
+}
+
+// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
+//
+// Note: This option only applicable to standard JSON Marshaller.
+func (r *Request) SetJSONEscapeHTML(b bool) *Request {
+ r.jsonEscapeHTML = b
+ return r
+}
+
+// SetCookie method appends a single cookie in the current request instance.
+// client.R().SetCookie(&http.Cookie{
+// Name:"go-resty",
+// Value:"This is cookie value",
+// })
+//
+// Note: Method appends the Cookie value into existing Cookie if already existing.
+//
+// Since v2.1.0
+func (r *Request) SetCookie(hc *http.Cookie) *Request {
+ r.Cookies = append(r.Cookies, hc)
+ return r
+}
+
+// SetCookies method sets an array of cookies in the current request instance.
+// cookies := []*http.Cookie{
+// &http.Cookie{
+// Name:"go-resty-1",
+// Value:"This is cookie 1 value",
+// },
+// &http.Cookie{
+// Name:"go-resty-2",
+// Value:"This is cookie 2 value",
+// },
+// }
+//
+// // Setting a cookies into resty's current request
+// client.R().SetCookies(cookies)
+//
+// Note: Method appends the Cookie value into existing Cookie if already existing.
+//
+// Since v2.1.0
+func (r *Request) SetCookies(rs []*http.Cookie) *Request {
+ r.Cookies = append(r.Cookies, rs...)
+ return r
+}
+
+// AddRetryCondition method adds a retry condition function to the request's
+// array of functions that are checked to determine if the request is retried.
+// The request will retry if any of the functions return true and error is nil.
+//
+// Note: These retry conditions are checked before all retry conditions of the client.
+//
+// Since v2.7.0
+func (r *Request) AddRetryCondition(condition RetryConditionFunc) *Request {
+ r.retryConditions = append(r.retryConditions, condition)
+ return r
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// HTTP request tracing
+//_______________________________________________________________________
+
+// EnableTrace method enables trace for the current request
+// using `httptrace.ClientTrace` and provides insights.
+//
+// client := resty.New()
+//
+// resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
+// fmt.Println("Error:", err)
+// fmt.Println("Trace Info:", resp.Request.TraceInfo())
+//
+// See `Client.EnableTrace` available too to get trace info for all requests.
+//
+// Since v2.0.0
+func (r *Request) EnableTrace() *Request {
+ r.trace = true
+ return r
+}
+
+// TraceInfo method returns the trace info for the request.
+// If either the Client or Request EnableTrace function has not been called
+// prior to the request being made, an empty TraceInfo object will be returned.
+//
+// Since v2.0.0
+func (r *Request) TraceInfo() TraceInfo {
+ ct := r.clientTrace
+
+ if ct == nil {
+ return TraceInfo{}
+ }
+
+ ti := TraceInfo{
+ DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
+ TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
+ ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn),
+ IsConnReused: ct.gotConnInfo.Reused,
+ IsConnWasIdle: ct.gotConnInfo.WasIdle,
+ ConnIdleTime: ct.gotConnInfo.IdleTime,
+ RequestAttempt: r.Attempt,
+ }
+
+ // Calculate the total time accordingly,
+ // when connection is reused
+ if ct.gotConnInfo.Reused {
+ ti.TotalTime = ct.endTime.Sub(ct.getConn)
+ } else {
+ ti.TotalTime = ct.endTime.Sub(ct.dnsStart)
+ }
+
+ // Only calculate on successful connections
+ if !ct.connectDone.IsZero() {
+ ti.TCPConnTime = ct.connectDone.Sub(ct.dnsDone)
+ }
+
+ // Only calculate on successful connections
+ if !ct.gotConn.IsZero() {
+ ti.ConnTime = ct.gotConn.Sub(ct.getConn)
+ }
+
+ // Only calculate on successful connections
+ if !ct.gotFirstResponseByte.IsZero() {
+ ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte)
+ }
+
+ // Capture remote address info when connection is non-nil
+ if ct.gotConnInfo.Conn != nil {
+ ti.RemoteAddr = ct.gotConnInfo.Conn.RemoteAddr()
+ }
+
+ return ti
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// HTTP verb method starts here
+//_______________________________________________________________________
+
+// Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
+func (r *Request) Get(url string) (*Response, error) {
+ return r.Execute(MethodGet, url)
+}
+
+// Head method does HEAD HTTP request. It's defined in section 4.3.2 of RFC7231.
+func (r *Request) Head(url string) (*Response, error) {
+ return r.Execute(MethodHead, url)
+}
+
+// Post method does POST HTTP request. It's defined in section 4.3.3 of RFC7231.
+func (r *Request) Post(url string) (*Response, error) {
+ return r.Execute(MethodPost, url)
+}
+
+// Put method does PUT HTTP request. It's defined in section 4.3.4 of RFC7231.
+func (r *Request) Put(url string) (*Response, error) {
+ return r.Execute(MethodPut, url)
+}
+
+// Delete method does DELETE HTTP request. It's defined in section 4.3.5 of RFC7231.
+func (r *Request) Delete(url string) (*Response, error) {
+ return r.Execute(MethodDelete, url)
+}
+
+// Options method does OPTIONS HTTP request. It's defined in section 4.3.7 of RFC7231.
+func (r *Request) Options(url string) (*Response, error) {
+ return r.Execute(MethodOptions, url)
+}
+
+// Patch method does PATCH HTTP request. It's defined in section 2 of RFC5789.
+func (r *Request) Patch(url string) (*Response, error) {
+ return r.Execute(MethodPatch, url)
+}
+
+// Send method performs the HTTP request using the method and URL already defined
+// for current `Request`.
+// req := client.R()
+// req.Method = resty.GET
+// req.URL = "http://httpbin.org/get"
+// resp, err := client.R().Send()
+func (r *Request) Send() (*Response, error) {
+ return r.Execute(r.Method, r.URL)
+}
+
+// Execute method performs the HTTP request with given HTTP method and URL
+// for current `Request`.
+// resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")
+func (r *Request) Execute(method, url string) (*Response, error) {
+ var addrs []*net.SRV
+ var resp *Response
+ var err error
+
+ if r.isMultiPart && !(method == MethodPost || method == MethodPut || method == MethodPatch) {
+ // No OnError hook here since this is a request validation error
+ return nil, fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method)
+ }
+
+ if r.SRV != nil {
+ _, addrs, err = net.LookupSRV(r.SRV.Service, "tcp", r.SRV.Domain)
+ if err != nil {
+ r.client.onErrorHooks(r, nil, err)
+ return nil, err
+ }
+ }
+
+ r.Method = method
+ r.URL = r.selectAddr(addrs, url, 0)
+
+ if r.client.RetryCount == 0 {
+ r.Attempt = 1
+ resp, err = r.client.execute(r)
+ r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
+ return resp, unwrapNoRetryErr(err)
+ }
+
+ err = Backoff(
+ func() (*Response, error) {
+ r.Attempt++
+
+ r.URL = r.selectAddr(addrs, url, r.Attempt)
+
+ resp, err = r.client.execute(r)
+ if err != nil {
+ r.client.log.Errorf("%v, Attempt %v", err, r.Attempt)
+ }
+
+ return resp, err
+ },
+ Retries(r.client.RetryCount),
+ WaitTime(r.client.RetryWaitTime),
+ MaxWaitTime(r.client.RetryMaxWaitTime),
+ RetryConditions(append(r.retryConditions, r.client.RetryConditions...)),
+ RetryHooks(r.client.RetryHooks),
+ )
+
+ r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err))
+
+ return resp, unwrapNoRetryErr(err)
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// SRVRecord struct
+//_______________________________________________________________________
+
+// SRVRecord struct holds the data to query the SRV record for the
+// following service.
+type SRVRecord struct {
+ Service string
+ Domain string
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Request Unexported methods
+//_______________________________________________________________________
+
+func (r *Request) fmtBodyString(sl int64) (body string) {
+ body = "***** NO CONTENT *****"
+ if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
+ return
+ }
+
+ if _, ok := r.Body.(io.Reader); ok {
+ body = "***** BODY IS io.Reader *****"
+ return
+ }
+
+ // multipart or form-data
+ if r.isMultiPart || r.isFormData {
+ bodySize := int64(r.bodyBuf.Len())
+ if bodySize > sl {
+ body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
+ return
+ }
+ body = r.bodyBuf.String()
+ return
+ }
+
+ // request body data
+ if r.Body == nil {
+ return
+ }
+ var prtBodyBytes []byte
+ var err error
+
+ contentType := r.Header.Get(hdrContentTypeKey)
+ kind := kindOf(r.Body)
+ if canJSONMarshal(contentType, kind) {
+ prtBodyBytes, err = json.MarshalIndent(&r.Body, "", " ")
+ } else if IsXMLType(contentType) && (kind == reflect.Struct) {
+ prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", " ")
+ } else if b, ok := r.Body.(string); ok {
+ if IsJSONType(contentType) {
+ bodyBytes := []byte(b)
+ out := acquireBuffer()
+ defer releaseBuffer(out)
+ if err = json.Indent(out, bodyBytes, "", " "); err == nil {
+ prtBodyBytes = out.Bytes()
+ }
+ } else {
+ body = b
+ }
+ } else if b, ok := r.Body.([]byte); ok {
+ body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b))
+ return
+ }
+
+ if prtBodyBytes != nil && err == nil {
+ body = string(prtBodyBytes)
+ }
+
+ if len(body) > 0 {
+ bodySize := int64(len([]byte(body)))
+ if bodySize > sl {
+ body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
+ }
+ }
+
+ return
+}
+
+func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string {
+ if addrs == nil {
+ return path
+ }
+
+ idx := attempt % len(addrs)
+ domain := strings.TrimRight(addrs[idx].Target, ".")
+ path = strings.TrimLeft(path, "/")
+
+ return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
+}
+
+func (r *Request) initValuesMap() {
+ if r.values == nil {
+ r.values = make(map[string]interface{})
+ }
+}
+
+var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) {
+ buf := acquireBuffer()
+ encoder := json.NewEncoder(buf)
+ encoder.SetEscapeHTML(false)
+ if err := encoder.Encode(v); err != nil {
+ releaseBuffer(buf)
+ return nil, err
+ }
+
+ return buf, nil
+}
diff --git a/vendor/github.com/go-resty/resty/v2/response.go b/vendor/github.com/go-resty/resty/v2/response.go
new file mode 100644
index 000000000..8ae0e10ba
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/response.go
@@ -0,0 +1,175 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+)
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Response struct and methods
+//_______________________________________________________________________
+
+// Response struct holds response values of executed request.
+type Response struct {
+ Request *Request
+ RawResponse *http.Response
+
+ body []byte
+ size int64
+ receivedAt time.Time
+}
+
+// Body method returns HTTP response as []byte array for the executed request.
+//
+// Note: `Response.Body` might be nil, if `Request.SetOutput` is used.
+func (r *Response) Body() []byte {
+ if r.RawResponse == nil {
+ return []byte{}
+ }
+ return r.body
+}
+
+// Status method returns the HTTP status string for the executed request.
+// Example: 200 OK
+func (r *Response) Status() string {
+ if r.RawResponse == nil {
+ return ""
+ }
+ return r.RawResponse.Status
+}
+
+// StatusCode method returns the HTTP status code for the executed request.
+// Example: 200
+func (r *Response) StatusCode() int {
+ if r.RawResponse == nil {
+ return 0
+ }
+ return r.RawResponse.StatusCode
+}
+
+// Proto method returns the HTTP response protocol used for the request.
+func (r *Response) Proto() string {
+ if r.RawResponse == nil {
+ return ""
+ }
+ return r.RawResponse.Proto
+}
+
+// Result method returns the response value as an object if it has one
+func (r *Response) Result() interface{} {
+ return r.Request.Result
+}
+
+// Error method returns the error object if it has one
+func (r *Response) Error() interface{} {
+ return r.Request.Error
+}
+
+// Header method returns the response headers
+func (r *Response) Header() http.Header {
+ if r.RawResponse == nil {
+ return http.Header{}
+ }
+ return r.RawResponse.Header
+}
+
+// Cookies method to access all the response cookies
+func (r *Response) Cookies() []*http.Cookie {
+ if r.RawResponse == nil {
+ return make([]*http.Cookie, 0)
+ }
+ return r.RawResponse.Cookies()
+}
+
+// String method returns the body of the server response as String.
+func (r *Response) String() string {
+ if r.body == nil {
+ return ""
+ }
+ return strings.TrimSpace(string(r.body))
+}
+
+// Time method returns the time of HTTP response time that from request we sent and received a request.
+//
+// See `Response.ReceivedAt` to know when client received response and see `Response.Request.Time` to know
+// when client sent a request.
+func (r *Response) Time() time.Duration {
+ if r.Request.clientTrace != nil {
+ return r.Request.TraceInfo().TotalTime
+ }
+ return r.receivedAt.Sub(r.Request.Time)
+}
+
+// ReceivedAt method returns when response got received from server for the request.
+func (r *Response) ReceivedAt() time.Time {
+ return r.receivedAt
+}
+
+// Size method returns the HTTP response size in bytes. Ya, you can relay on HTTP `Content-Length` header,
+// however it won't be good for chucked transfer/compressed response. Since Resty calculates response size
+// at the client end. You will get actual size of the http response.
+func (r *Response) Size() int64 {
+ return r.size
+}
+
+// RawBody method exposes the HTTP raw response body. Use this method in-conjunction with `SetDoNotParseResponse`
+// option otherwise you get an error as `read err: http: read on closed response body`.
+//
+// Do not forget to close the body, otherwise you might get into connection leaks, no connection reuse.
+// Basically you have taken over the control of response parsing from `Resty`.
+func (r *Response) RawBody() io.ReadCloser {
+ if r.RawResponse == nil {
+ return nil
+ }
+ return r.RawResponse.Body
+}
+
+// IsSuccess method returns true if HTTP status `code >= 200 and <= 299` otherwise false.
+func (r *Response) IsSuccess() bool {
+ return r.StatusCode() > 199 && r.StatusCode() < 300
+}
+
+// IsError method returns true if HTTP status `code >= 400` otherwise false.
+func (r *Response) IsError() bool {
+ return r.StatusCode() > 399
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Response Unexported methods
+//_______________________________________________________________________
+
+func (r *Response) setReceivedAt() {
+ r.receivedAt = time.Now()
+ if r.Request.clientTrace != nil {
+ r.Request.clientTrace.endTime = r.receivedAt
+ }
+}
+
+func (r *Response) fmtBodyString(sl int64) string {
+ if r.body != nil {
+ if int64(len(r.body)) > sl {
+ return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", len(r.body))
+ }
+ ct := r.Header().Get(hdrContentTypeKey)
+ if IsJSONType(ct) {
+ out := acquireBuffer()
+ defer releaseBuffer(out)
+ err := json.Indent(out, r.body, "", " ")
+ if err != nil {
+ return fmt.Sprintf("*** Error: Unable to format response body - \"%s\" ***\n\nLog Body as-is:\n%s", err, r.String())
+ }
+ return out.String()
+ }
+ return r.String()
+ }
+
+ return "***** NO CONTENT *****"
+}
diff --git a/vendor/github.com/go-resty/resty/v2/resty.go b/vendor/github.com/go-resty/resty/v2/resty.go
new file mode 100644
index 000000000..6f9c8b4cd
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/resty.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+// Package resty provides Simple HTTP and REST client library for Go.
+package resty
+
+import (
+ "net"
+ "net/http"
+ "net/http/cookiejar"
+
+ "golang.org/x/net/publicsuffix"
+)
+
+// Version # of resty
+const Version = "2.7.0"
+
+// New method creates a new Resty client.
+func New() *Client {
+ cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+ return createClient(&http.Client{
+ Jar: cookieJar,
+ })
+}
+
+// NewWithClient method creates a new Resty client with given `http.Client`.
+func NewWithClient(hc *http.Client) *Client {
+ return createClient(hc)
+}
+
+// NewWithLocalAddr method creates a new Resty client with given Local Address
+// to dial from.
+func NewWithLocalAddr(localAddr net.Addr) *Client {
+ cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+ return createClient(&http.Client{
+ Jar: cookieJar,
+ Transport: createTransport(localAddr),
+ })
+}
diff --git a/vendor/github.com/go-resty/resty/v2/retry.go b/vendor/github.com/go-resty/resty/v2/retry.go
new file mode 100644
index 000000000..00b8514a5
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/retry.go
@@ -0,0 +1,221 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "context"
+ "math"
+ "math/rand"
+ "sync"
+ "time"
+)
+
+const (
+ defaultMaxRetries = 3
+ defaultWaitTime = time.Duration(100) * time.Millisecond
+ defaultMaxWaitTime = time.Duration(2000) * time.Millisecond
+)
+
+type (
+ // Option is to create convenient retry options like wait time, max retries, etc.
+ Option func(*Options)
+
+ // RetryConditionFunc type is for retry condition function
+ // input: non-nil Response OR request execution error
+ RetryConditionFunc func(*Response, error) bool
+
+ // OnRetryFunc is for side-effecting functions triggered on retry
+ OnRetryFunc func(*Response, error)
+
+ // RetryAfterFunc returns time to wait before retry
+ // For example, it can parse HTTP Retry-After header
+ // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ // Non-nil error is returned if it is found that request is not retryable
+ // (0, nil) is a special result means 'use default algorithm'
+ RetryAfterFunc func(*Client, *Response) (time.Duration, error)
+
+ // Options struct is used to hold retry settings.
+ Options struct {
+ maxRetries int
+ waitTime time.Duration
+ maxWaitTime time.Duration
+ retryConditions []RetryConditionFunc
+ retryHooks []OnRetryFunc
+ }
+)
+
+// Retries sets the max number of retries
+func Retries(value int) Option {
+ return func(o *Options) {
+ o.maxRetries = value
+ }
+}
+
+// WaitTime sets the default wait time to sleep between requests
+func WaitTime(value time.Duration) Option {
+ return func(o *Options) {
+ o.waitTime = value
+ }
+}
+
+// MaxWaitTime sets the max wait time to sleep between requests
+func MaxWaitTime(value time.Duration) Option {
+ return func(o *Options) {
+ o.maxWaitTime = value
+ }
+}
+
+// RetryConditions sets the conditions that will be checked for retry.
+func RetryConditions(conditions []RetryConditionFunc) Option {
+ return func(o *Options) {
+ o.retryConditions = conditions
+ }
+}
+
+// RetryHooks sets the hooks that will be executed after each retry
+func RetryHooks(hooks []OnRetryFunc) Option {
+ return func(o *Options) {
+ o.retryHooks = hooks
+ }
+}
+
+// Backoff retries with increasing timeout duration up until X amount of retries
+// (Default is 3 attempts, Override with option Retries(n))
+func Backoff(operation func() (*Response, error), options ...Option) error {
+ // Defaults
+ opts := Options{
+ maxRetries: defaultMaxRetries,
+ waitTime: defaultWaitTime,
+ maxWaitTime: defaultMaxWaitTime,
+ retryConditions: []RetryConditionFunc{},
+ }
+
+ for _, o := range options {
+ o(&opts)
+ }
+
+ var (
+ resp *Response
+ err error
+ )
+
+ for attempt := 0; attempt <= opts.maxRetries; attempt++ {
+ resp, err = operation()
+ ctx := context.Background()
+ if resp != nil && resp.Request.ctx != nil {
+ ctx = resp.Request.ctx
+ }
+ if ctx.Err() != nil {
+ return err
+ }
+
+ err1 := unwrapNoRetryErr(err) // raw error, it used for return users callback.
+ needsRetry := err != nil && err == err1 // retry on a few operation errors by default
+
+ for _, condition := range opts.retryConditions {
+ needsRetry = condition(resp, err1)
+ if needsRetry {
+ break
+ }
+ }
+
+ if !needsRetry {
+ return err
+ }
+
+ for _, hook := range opts.retryHooks {
+ hook(resp, err)
+ }
+
+ // Don't need to wait when no retries left.
+ // Still run retry hooks even on last retry to keep compatibility.
+ if attempt == opts.maxRetries {
+ return err
+ }
+
+ waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
+ if err2 != nil {
+ if err == nil {
+ err = err2
+ }
+ return err
+ }
+
+ select {
+ case <-time.After(waitTime):
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+ }
+
+ return err
+}
+
+func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) {
+ const maxInt = 1<<31 - 1 // max int for arch 386
+ if max < 0 {
+ max = maxInt
+ }
+ if resp == nil {
+ return jitterBackoff(min, max, attempt), nil
+ }
+
+ retryAfterFunc := resp.Request.client.RetryAfter
+
+ // Check for custom callback
+ if retryAfterFunc == nil {
+ return jitterBackoff(min, max, attempt), nil
+ }
+
+ result, err := retryAfterFunc(resp.Request.client, resp)
+ if err != nil {
+ return 0, err // i.e. 'API quota exceeded'
+ }
+ if result == 0 {
+ return jitterBackoff(min, max, attempt), nil
+ }
+ if result < 0 || max < result {
+ result = max
+ }
+ if result < min {
+ result = min
+ }
+ return result, nil
+}
+
+// Return capped exponential backoff with jitter
+// http://www.awsarchitectureblog.com/2015/03/backoff.html
+func jitterBackoff(min, max time.Duration, attempt int) time.Duration {
+ base := float64(min)
+ capLevel := float64(max)
+
+ temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
+ ri := time.Duration(temp / 2)
+ result := randDuration(ri)
+
+ if result < min {
+ result = min
+ }
+
+ return result
+}
+
+var rnd = newRnd()
+var rndMu sync.Mutex
+
+func randDuration(center time.Duration) time.Duration {
+ rndMu.Lock()
+ defer rndMu.Unlock()
+
+ var ri = int64(center)
+ var jitter = rnd.Int63n(ri)
+ return time.Duration(math.Abs(float64(ri + jitter)))
+}
+
+func newRnd() *rand.Rand {
+ var seed = time.Now().UnixNano()
+ var src = rand.NewSource(seed)
+ return rand.New(src)
+}
diff --git a/vendor/github.com/go-resty/resty/v2/trace.go b/vendor/github.com/go-resty/resty/v2/trace.go
new file mode 100644
index 000000000..23cf70335
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/trace.go
@@ -0,0 +1,130 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "context"
+ "crypto/tls"
+ "net"
+ "net/http/httptrace"
+ "time"
+)
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// TraceInfo struct
+//_______________________________________________________________________
+
+// TraceInfo struct is used provide request trace info such as DNS lookup
+// duration, Connection obtain duration, Server processing duration, etc.
+//
+// Since v2.0.0
+type TraceInfo struct {
+ // DNSLookup is a duration that transport took to perform
+ // DNS lookup.
+ DNSLookup time.Duration
+
+ // ConnTime is a duration that took to obtain a successful connection.
+ ConnTime time.Duration
+
+ // TCPConnTime is a duration that took to obtain the TCP connection.
+ TCPConnTime time.Duration
+
+ // TLSHandshake is a duration that TLS handshake took place.
+ TLSHandshake time.Duration
+
+ // ServerTime is a duration that server took to respond first byte.
+ ServerTime time.Duration
+
+ // ResponseTime is a duration since first response byte from server to
+ // request completion.
+ ResponseTime time.Duration
+
+ // TotalTime is a duration that total request took end-to-end.
+ TotalTime time.Duration
+
+ // IsConnReused is whether this connection has been previously
+ // used for another HTTP request.
+ IsConnReused bool
+
+ // IsConnWasIdle is whether this connection was obtained from an
+ // idle pool.
+ IsConnWasIdle bool
+
+ // ConnIdleTime is a duration how long the connection was previously
+ // idle, if IsConnWasIdle is true.
+ ConnIdleTime time.Duration
+
+ // RequestAttempt is to represent the request attempt made during a Resty
+ // request execution flow, including retry count.
+ RequestAttempt int
+
+ // RemoteAddr returns the remote network address.
+ RemoteAddr net.Addr
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// ClientTrace struct and its methods
+//_______________________________________________________________________
+
+// tracer struct maps the `httptrace.ClientTrace` hooks into Fields
+// with same naming for easy understanding. Plus additional insights
+// Request.
+type clientTrace struct {
+ getConn time.Time
+ dnsStart time.Time
+ dnsDone time.Time
+ connectDone time.Time
+ tlsHandshakeStart time.Time
+ tlsHandshakeDone time.Time
+ gotConn time.Time
+ gotFirstResponseByte time.Time
+ endTime time.Time
+ gotConnInfo httptrace.GotConnInfo
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Trace unexported methods
+//_______________________________________________________________________
+
+func (t *clientTrace) createContext(ctx context.Context) context.Context {
+ return httptrace.WithClientTrace(
+ ctx,
+ &httptrace.ClientTrace{
+ DNSStart: func(_ httptrace.DNSStartInfo) {
+ t.dnsStart = time.Now()
+ },
+ DNSDone: func(_ httptrace.DNSDoneInfo) {
+ t.dnsDone = time.Now()
+ },
+ ConnectStart: func(_, _ string) {
+ if t.dnsDone.IsZero() {
+ t.dnsDone = time.Now()
+ }
+ if t.dnsStart.IsZero() {
+ t.dnsStart = t.dnsDone
+ }
+ },
+ ConnectDone: func(net, addr string, err error) {
+ t.connectDone = time.Now()
+ },
+ GetConn: func(_ string) {
+ t.getConn = time.Now()
+ },
+ GotConn: func(ci httptrace.GotConnInfo) {
+ t.gotConn = time.Now()
+ t.gotConnInfo = ci
+ },
+ GotFirstResponseByte: func() {
+ t.gotFirstResponseByte = time.Now()
+ },
+ TLSHandshakeStart: func() {
+ t.tlsHandshakeStart = time.Now()
+ },
+ TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
+ t.tlsHandshakeDone = time.Now()
+ },
+ },
+ )
+}
diff --git a/vendor/github.com/go-resty/resty/v2/transport.go b/vendor/github.com/go-resty/resty/v2/transport.go
new file mode 100644
index 000000000..e15b48c55
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/transport.go
@@ -0,0 +1,35 @@
+// +build go1.13
+
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "net"
+ "net/http"
+ "runtime"
+ "time"
+)
+
+func createTransport(localAddr net.Addr) *http.Transport {
+ dialer := &net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }
+ if localAddr != nil {
+ dialer.LocalAddr = localAddr
+ }
+ return &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: dialer.DialContext,
+ ForceAttemptHTTP2: true,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
+ }
+}
diff --git a/vendor/github.com/go-resty/resty/v2/transport112.go b/vendor/github.com/go-resty/resty/v2/transport112.go
new file mode 100644
index 000000000..fbbbc5911
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/transport112.go
@@ -0,0 +1,34 @@
+// +build !go1.13
+
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "net"
+ "net/http"
+ "runtime"
+ "time"
+)
+
+func createTransport(localAddr net.Addr) *http.Transport {
+ dialer := &net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }
+ if localAddr != nil {
+ dialer.LocalAddr = localAddr
+ }
+ return &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: dialer.DialContext,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
+ }
+}
diff --git a/vendor/github.com/go-resty/resty/v2/util.go b/vendor/github.com/go-resty/resty/v2/util.go
new file mode 100644
index 000000000..1d563befd
--- /dev/null
+++ b/vendor/github.com/go-resty/resty/v2/util.go
@@ -0,0 +1,391 @@
+// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log"
+ "mime/multipart"
+ "net/http"
+ "net/textproto"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "strings"
+ "sync"
+)
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Logger interface
+//_______________________________________________________________________
+
+// Logger interface is to abstract the logging from Resty. Gives control to
+// the Resty users, choice of the logger.
+type Logger interface {
+ Errorf(format string, v ...interface{})
+ Warnf(format string, v ...interface{})
+ Debugf(format string, v ...interface{})
+}
+
+func createLogger() *logger {
+ l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
+ return l
+}
+
+var _ Logger = (*logger)(nil)
+
+type logger struct {
+ l *log.Logger
+}
+
+func (l *logger) Errorf(format string, v ...interface{}) {
+ l.output("ERROR RESTY "+format, v...)
+}
+
+func (l *logger) Warnf(format string, v ...interface{}) {
+ l.output("WARN RESTY "+format, v...)
+}
+
+func (l *logger) Debugf(format string, v ...interface{}) {
+ l.output("DEBUG RESTY "+format, v...)
+}
+
+func (l *logger) output(format string, v ...interface{}) {
+ if len(v) == 0 {
+ l.l.Print(format)
+ return
+ }
+ l.l.Printf(format, v...)
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Package Helper methods
+//_______________________________________________________________________
+
+// IsStringEmpty method tells whether given string is empty or not
+func IsStringEmpty(str string) bool {
+ return len(strings.TrimSpace(str)) == 0
+}
+
+// DetectContentType method is used to figure out `Request.Body` content type for request header
+func DetectContentType(body interface{}) string {
+ contentType := plainTextType
+ kind := kindOf(body)
+ switch kind {
+ case reflect.Struct, reflect.Map:
+ contentType = jsonContentType
+ case reflect.String:
+ contentType = plainTextType
+ default:
+ if b, ok := body.([]byte); ok {
+ contentType = http.DetectContentType(b)
+ } else if kind == reflect.Slice {
+ contentType = jsonContentType
+ }
+ }
+
+ return contentType
+}
+
+// IsJSONType method is to check JSON content type or not
+func IsJSONType(ct string) bool {
+ return jsonCheck.MatchString(ct)
+}
+
+// IsXMLType method is to check XML content type or not
+func IsXMLType(ct string) bool {
+ return xmlCheck.MatchString(ct)
+}
+
+// Unmarshalc content into object from JSON or XML
+func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
+ if IsJSONType(ct) {
+ err = c.JSONUnmarshal(b, d)
+ } else if IsXMLType(ct) {
+ err = c.XMLUnmarshal(b, d)
+ }
+
+ return
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// RequestLog and ResponseLog type
+//_______________________________________________________________________
+
+// RequestLog struct is used to collected information from resty request
+// instance for debug logging. It sent to request log callback before resty
+// actually logs the information.
+type RequestLog struct {
+ Header http.Header
+ Body string
+}
+
+// ResponseLog struct is used to collected information from resty response
+// instance for debug logging. It sent to response log callback before resty
+// actually logs the information.
+type ResponseLog struct {
+ Header http.Header
+ Body string
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Package Unexported methods
+//_______________________________________________________________________
+
+// way to disable the HTML escape as opt-in
+func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) {
+ if !r.jsonEscapeHTML || !c.jsonEscapeHTML {
+ return noescapeJSONMarshal(d)
+ }
+
+ data, err := c.JSONMarshal(d)
+ if err != nil {
+ return nil, err
+ }
+
+ buf := acquireBuffer()
+ _, _ = buf.Write(data)
+ return buf, nil
+}
+
+func firstNonEmpty(v ...string) string {
+ for _, s := range v {
+ if !IsStringEmpty(s) {
+ return s
+ }
+ }
+ return ""
+}
+
+var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
+
+func escapeQuotes(s string) string {
+ return quoteEscaper.Replace(s)
+}
+
+func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
+ hdr := make(textproto.MIMEHeader)
+
+ var contentDispositionValue string
+ if IsStringEmpty(fileName) {
+ contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
+ } else {
+ contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
+ param, escapeQuotes(fileName))
+ }
+ hdr.Set("Content-Disposition", contentDispositionValue)
+
+ if !IsStringEmpty(contentType) {
+ hdr.Set(hdrContentTypeKey, contentType)
+ }
+ return hdr
+}
+
+func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error {
+ partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType))
+ if err != nil {
+ return err
+ }
+
+ _, err = io.Copy(partWriter, mf.Reader)
+ return err
+}
+
+func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {
+ // Auto detect actual multipart content type
+ cbuf := make([]byte, 512)
+ size, err := r.Read(cbuf)
+ if err != nil && err != io.EOF {
+ return err
+ }
+
+ partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf)))
+ if err != nil {
+ return err
+ }
+
+ if _, err = partWriter.Write(cbuf[:size]); err != nil {
+ return err
+ }
+
+ _, err = io.Copy(partWriter, r)
+ return err
+}
+
+func addFile(w *multipart.Writer, fieldName, path string) error {
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer closeq(file)
+ return writeMultipartFormFile(w, fieldName, filepath.Base(path), file)
+}
+
+func addFileReader(w *multipart.Writer, f *File) error {
+ return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
+}
+
+func getPointer(v interface{}) interface{} {
+ vv := valueOf(v)
+ if vv.Kind() == reflect.Ptr {
+ return v
+ }
+ return reflect.New(vv.Type()).Interface()
+}
+
+func isPayloadSupported(m string, allowMethodGet bool) bool {
+ return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
+}
+
+func typeOf(i interface{}) reflect.Type {
+ return indirect(valueOf(i)).Type()
+}
+
+func valueOf(i interface{}) reflect.Value {
+ return reflect.ValueOf(i)
+}
+
+func indirect(v reflect.Value) reflect.Value {
+ return reflect.Indirect(v)
+}
+
+func kindOf(v interface{}) reflect.Kind {
+ return typeOf(v).Kind()
+}
+
+func createDirectory(dir string) (err error) {
+ if _, err = os.Stat(dir); err != nil {
+ if os.IsNotExist(err) {
+ if err = os.MkdirAll(dir, 0755); err != nil {
+ return
+ }
+ }
+ }
+ return
+}
+
+func canJSONMarshal(contentType string, kind reflect.Kind) bool {
+ return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
+}
+
+func functionName(i interface{}) string {
+ return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+}
+
+func acquireBuffer() *bytes.Buffer {
+ return bufPool.Get().(*bytes.Buffer)
+}
+
+func releaseBuffer(buf *bytes.Buffer) {
+ if buf != nil {
+ buf.Reset()
+ bufPool.Put(buf)
+ }
+}
+
+// requestBodyReleaser wraps requests's body and implements custom Close for it.
+// The Close method closes original body and releases request body back to sync.Pool.
+type requestBodyReleaser struct {
+ releaseOnce sync.Once
+ reqBuf *bytes.Buffer
+ io.ReadCloser
+}
+
+func newRequestBodyReleaser(respBody io.ReadCloser, reqBuf *bytes.Buffer) io.ReadCloser {
+ if reqBuf == nil {
+ return respBody
+ }
+
+ return &requestBodyReleaser{
+ reqBuf: reqBuf,
+ ReadCloser: respBody,
+ }
+}
+
+func (rr *requestBodyReleaser) Close() error {
+ err := rr.ReadCloser.Close()
+ rr.releaseOnce.Do(func() {
+ releaseBuffer(rr.reqBuf)
+ })
+
+ return err
+}
+
+func closeq(v interface{}) {
+ if c, ok := v.(io.Closer); ok {
+ silently(c.Close())
+ }
+}
+
+func silently(_ ...interface{}) {}
+
+func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
+ str := make([]string, 0, len(hdrs))
+ for _, k := range sortHeaderKeys(hdrs) {
+ var v string
+ if k == "Cookie" {
+ cv := strings.TrimSpace(strings.Join(hdrs[k], ", "))
+ if c.GetClient().Jar != nil {
+ for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
+ if cv != "" {
+ cv = cv + "; " + c.String()
+ } else {
+ cv = c.String()
+ }
+ }
+ }
+ v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv))
+ } else {
+ v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
+ }
+ if v != "" {
+ str = append(str, "\t"+v)
+ }
+ }
+ return strings.Join(str, "\n")
+}
+
+func sortHeaderKeys(hdrs http.Header) []string {
+ keys := make([]string, 0, len(hdrs))
+ for key := range hdrs {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ return keys
+}
+
+func copyHeaders(hdrs http.Header) http.Header {
+ nh := http.Header{}
+ for k, v := range hdrs {
+ nh[k] = v
+ }
+ return nh
+}
+
+type noRetryErr struct {
+ err error
+}
+
+func (e *noRetryErr) Error() string {
+ return e.err.Error()
+}
+
+func wrapNoRetryErr(err error) error {
+ if err != nil {
+ err = &noRetryErr{err: err}
+ }
+ return err
+}
+
+func unwrapNoRetryErr(err error) error {
+ if e, ok := err.(*noRetryErr); ok {
+ err = e.err
+ }
+ return err
+}
diff --git a/vendor/golang.org/x/net/publicsuffix/list.go b/vendor/golang.org/x/net/publicsuffix/list.go
new file mode 100644
index 000000000..e2fddd645
--- /dev/null
+++ b/vendor/golang.org/x/net/publicsuffix/list.go
@@ -0,0 +1,182 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run gen.go
+
+// Package publicsuffix provides a public suffix list based on data from
+// https://publicsuffix.org/
+//
+// A public suffix is one under which Internet users can directly register
+// names. It is related to, but different from, a TLD (top level domain).
+//
+// "com" is a TLD (top level domain). Top level means it has no dots.
+//
+// "com" is also a public suffix. Amazon and Google have registered different
+// siblings under that domain: "amazon.com" and "google.com".
+//
+// "au" is another TLD, again because it has no dots. But it's not "amazon.au".
+// Instead, it's "amazon.com.au".
+//
+// "com.au" isn't an actual TLD, because it's not at the top level (it has
+// dots). But it is an eTLD (effective TLD), because that's the branching point
+// for domain name registrars.
+//
+// Another name for "an eTLD" is "a public suffix". Often, what's more of
+// interest is the eTLD+1, or one more label than the public suffix. For
+// example, browsers partition read/write access to HTTP cookies according to
+// the eTLD+1. Web pages served from "amazon.com.au" can't read cookies from
+// "google.com.au", but web pages served from "maps.google.com" can share
+// cookies from "www.google.com", so you don't have to sign into Google Maps
+// separately from signing into Google Web Search. Note that all four of those
+// domains have 3 labels and 2 dots. The first two domains are each an eTLD+1,
+// the last two are not (but share the same eTLD+1: "google.com").
+//
+// All of these domains have the same eTLD+1:
+// - "www.books.amazon.co.uk"
+// - "books.amazon.co.uk"
+// - "amazon.co.uk"
+//
+// Specifically, the eTLD+1 is "amazon.co.uk", because the eTLD is "co.uk".
+//
+// There is no closed form algorithm to calculate the eTLD of a domain.
+// Instead, the calculation is data driven. This package provides a
+// pre-compiled snapshot of Mozilla's PSL (Public Suffix List) data at
+// https://publicsuffix.org/
+package publicsuffix // import "golang.org/x/net/publicsuffix"
+
+// TODO: specify case sensitivity and leading/trailing dot behavior for
+// func PublicSuffix and func EffectiveTLDPlusOne.
+
+import (
+ "fmt"
+ "net/http/cookiejar"
+ "strings"
+)
+
+// List implements the cookiejar.PublicSuffixList interface by calling the
+// PublicSuffix function.
+var List cookiejar.PublicSuffixList = list{}
+
+type list struct{}
+
+func (list) PublicSuffix(domain string) string {
+ ps, _ := PublicSuffix(domain)
+ return ps
+}
+
+func (list) String() string {
+ return version
+}
+
+// PublicSuffix returns the public suffix of the domain using a copy of the
+// publicsuffix.org database compiled into the library.
+//
+// icann is whether the public suffix is managed by the Internet Corporation
+// for Assigned Names and Numbers. If not, the public suffix is either a
+// privately managed domain (and in practice, not a top level domain) or an
+// unmanaged top level domain (and not explicitly mentioned in the
+// publicsuffix.org list). For example, "foo.org" and "foo.co.uk" are ICANN
+// domains, "foo.dyndns.org" and "foo.blogspot.co.uk" are private domains and
+// "cromulent" is an unmanaged top level domain.
+//
+// Use cases for distinguishing ICANN domains like "foo.com" from private
+// domains like "foo.appspot.com" can be found at
+// https://wiki.mozilla.org/Public_Suffix_List/Use_Cases
+func PublicSuffix(domain string) (publicSuffix string, icann bool) {
+ lo, hi := uint32(0), uint32(numTLD)
+ s, suffix, icannNode, wildcard := domain, len(domain), false, false
+loop:
+ for {
+ dot := strings.LastIndex(s, ".")
+ if wildcard {
+ icann = icannNode
+ suffix = 1 + dot
+ }
+ if lo == hi {
+ break
+ }
+ f := find(s[1+dot:], lo, hi)
+ if f == notFound {
+ break
+ }
+
+ u := nodes[f] >> (nodesBitsTextOffset + nodesBitsTextLength)
+ icannNode = u&(1<>= nodesBitsICANN
+ u = children[u&(1<>= childrenBitsLo
+ hi = u & (1<>= childrenBitsHi
+ switch u & (1<>= childrenBitsNodeType
+ wildcard = u&(1<>= nodesBitsTextLength
+ offset := x & (1<
Date: Wed, 23 Nov 2022 17:57:42 +0000
Subject: [PATCH 03/21] fix lint
---
src/plugins/agent_api_test.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index a17742688..a67485a4c 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -113,7 +113,9 @@ func TestMtlsForApi(t *testing.T) {
fmt.Println()
var details []*proto.NginxDetails
- json.Unmarshal(resp.Body(),&details)
+ err = json.Unmarshal(resp.Body(),&details)
+
+ assert.NoError(t, err)
// var responseItems map[string]*proto.NginxDetails
expected := tutils.GetDetailsMap()["12345"]
assert.Len(t, details, 1)
From 8d62bb1e124ee366346f993f91d1c0e432f43c76 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Wed, 23 Nov 2022 18:01:17 +0000
Subject: [PATCH 04/21] removed unnecessary changes
---
nginx-agent.conf | 2 --
src/core/metrics/collectors/nginx_test.go | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/nginx-agent.conf b/nginx-agent.conf
index b2b2bd26a..ebd24b4e9 100644
--- a/nginx-agent.conf
+++ b/nginx-agent.conf
@@ -18,8 +18,6 @@ server:
api:
# port to expose http api
port: 9090
- cert: "ca.pem"
- key: "server.key"
# tls options
tls:
# enable tls in the nginx-agent setup for grpcs
diff --git a/src/core/metrics/collectors/nginx_test.go b/src/core/metrics/collectors/nginx_test.go
index c88fa7e9f..7dbc265d7 100644
--- a/src/core/metrics/collectors/nginx_test.go
+++ b/src/core/metrics/collectors/nginx_test.go
@@ -184,7 +184,7 @@ func TestNginxCollector_UpdateConfig(t *testing.T) {
mockNginxSource2,
},
collectorConf: &metrics.NginxCollectorConfig{
- NginxId: "124",
+ NginxId: "123",
},
env: env,
}
From 69b65743fa771cf9d21d2877c647011ef80e6f92 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Fri, 25 Nov 2022 16:03:08 +0000
Subject: [PATCH 05/21] changes
---
scripts/mtls/ca-intermediate.cnf | 18 +++
scripts/mtls/ca.cnf | 21 ++++
scripts/mtls/client.cnf | 19 +++
scripts/mtls/gen_cnf.sh | 12 +-
scripts/mtls/make_certs.sh | 94 +++++++++++++++
scripts/mtls/server.cnf | 27 +++++
src/plugins/agent_api.go | 5 +
src/plugins/agent_api_test.go | 197 +++++++++++++++++++++++++++----
8 files changed, 362 insertions(+), 31 deletions(-)
create mode 100644 scripts/mtls/ca-intermediate.cnf
create mode 100644 scripts/mtls/ca.cnf
create mode 100644 scripts/mtls/client.cnf
create mode 100755 scripts/mtls/make_certs.sh
create mode 100644 scripts/mtls/server.cnf
diff --git a/scripts/mtls/ca-intermediate.cnf b/scripts/mtls/ca-intermediate.cnf
new file mode 100644
index 000000000..6a0548c1b
--- /dev/null
+++ b/scripts/mtls/ca-intermediate.cnf
@@ -0,0 +1,18 @@
+[req]
+default_bits = 4096
+distinguished_name = req_distinguished_name
+prompt = no
+default_md = sha512
+req_extensions = v3_req
+
+[req_distinguished_name]
+countryName = IE
+stateOrProvinceName = Munster
+localityName = Cork
+organizationName = NGINX, Inc.
+commonName = agent-int-ca
+
+[v3_req]
+basicConstraints = critical, CA:true
+keyUsage = critical, keyCertSign, cRLSign
+subjectKeyIdentifier = hash
diff --git a/scripts/mtls/ca.cnf b/scripts/mtls/ca.cnf
new file mode 100644
index 000000000..e6fb703e6
--- /dev/null
+++ b/scripts/mtls/ca.cnf
@@ -0,0 +1,21 @@
+[req]
+default_bits = 4096
+distinguished_name = req_distinguished_name
+prompt = no
+default_md = sha512
+req_extensions = v3_req
+
+[req_distinguished_name]
+countryName = IE
+stateOrProvinceName = Munster
+localityName = Cork
+organizationName = NGINX, Inc.
+commonName = agent-int-ca
+
+[v3_req]
+basicConstraints = critical, CA:true
+keyUsage = critical, keyCertSign, cRLSign
+subjectKeyIdentifier = hash
+
+[ policy_anything ]
+commonName = supplied
diff --git a/scripts/mtls/client.cnf b/scripts/mtls/client.cnf
new file mode 100644
index 000000000..2772c6281
--- /dev/null
+++ b/scripts/mtls/client.cnf
@@ -0,0 +1,19 @@
+[req]
+prompt = no
+default_bits = 2048
+x509_extensions = v3_req
+req_extensions = v3_req
+default_md = sha512
+distinguished_name = req_distinguished_name
+
+[req_distinguished_name]
+countryName = IE
+stateOrProvinceName = Munster
+localityName = Cork
+organizationName = NGINX, Inc.
+commonName = agent.example.com
+
+[v3_req]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
+extendedKeyUsage = critical, clientAuth
diff --git a/scripts/mtls/gen_cnf.sh b/scripts/mtls/gen_cnf.sh
index ce28978ec..818198353 100755
--- a/scripts/mtls/gen_cnf.sh
+++ b/scripts/mtls/gen_cnf.sh
@@ -125,10 +125,10 @@ req_extensions = v3_req
policy = policy
[ policy ]
-countryName = optional
-stateOrProvinceName = optional
+countryName = match
+stateOrProvinceName = match
+organizationName = match
localityName = optional
-organizationName = optional
organizationalUnitName = optional
commonName = supplied
@@ -163,10 +163,10 @@ req_extensions = v3_req
policy = policy
[ policy ]
-countryName = optional
-stateOrProvinceName = optional
+countryName = match
+stateOrProvinceName = match
+organizationName = match
localityName = optional
-organizationName = optional
organizationalUnitName = optional
commonName = supplied
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
new file mode 100755
index 000000000..f5ad126a5
--- /dev/null
+++ b/scripts/mtls/make_certs.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+set -e
+
+if [ ! -d ../../build ]; then
+ mkdir -p ../../build/certs;
+fi
+
+make_ca() {
+ echo "Creating Self-Signed Root CA certificate and key"
+ openssl req \
+ -newkey rsa:1024 \
+ -nodes \
+ -x509 \
+ -keyout ../../build/certs/ca.key \
+ -out ../../build/certs/ca.crt \
+ -config ca.cnf \
+ -extensions v3_req \
+ -days 1
+}
+
+make_int() {
+ echo "Creating Intermediate CA certificate and key"
+ openssl req \
+ -newkey rsa:1024 \
+ -nodes \
+ -keyout ../../build/certs/ca_int.key \
+ -out ../../build/certs/ca_int.csr \
+ -config ca-intermediate.cnf \
+ -extensions v3_req
+ openssl req -in ../../build/certs/ca_int.csr -noout -verify
+ openssl x509 \
+ -req \
+ -CA ../../build/certs/ca.crt \
+ -CAkey ../../build/certs/ca.key \
+ -CAcreateserial \
+ -in ../../build/certs/ca_int.csr \
+ -out ../../build/certs/ca_int.crt \
+ -extfile ca-intermediate.cnf \
+ -extensions v3_req \
+ -days 1
+ openssl verify -CAfile ../../build/certs/ca.crt ../../build/certs/ca_int.crt
+ echo "Creating CA chain"
+ cat ../../build/certs/ca_int.crt ../../build/certs/ca.crt > ../../build/certs/ca.pem
+}
+
+make_server() {
+ echo "Creating nginx-manger certificate and key"
+ openssl req \
+ -newkey rsa:1024 \
+ -nodes \
+ -keyout ../../build/certs/server.key \
+ -out ../../build/certs/server.csr \
+ -config server.cnf
+ openssl req -in ../../build/certs/server.csr -noout -verify
+ openssl x509 \
+ -req \
+ -CA ../../build/certs/ca_int.crt \
+ -CAkey ../../build/certs/ca_int.key \
+ -CAcreateserial \
+ -in ../../build/certs/server.csr \
+ -out ../../build/certs/server.crt \
+ -extfile server.cnf \
+ -extensions v3_req \
+ -days 1
+ openssl verify -CAfile ../../build/certs/ca.pem ../../build/certs/server.crt
+}
+
+make_client() {
+ echo "Creating Client certificate and key"
+ openssl req \
+ -newkey rsa:1024 \
+ -nodes \
+ -keyout ../../build/certs/client.key \
+ -out ../../build/certs/client.csr \
+ -config client.cnf
+ openssl req -in ../../build/certs/client.csr -noout -verify
+ openssl x509 \
+ -req \
+ -CA ../../build/certs/ca.crt \
+ -CAkey ../../build/certs/ca.key \
+ -CAcreateserial \
+ -in ../../build/certs/client.csr \
+ -out ../../build/certs/client.crt \
+ -extfile client.cnf \
+ -extensions v3_req \
+ -days 1
+ openssl verify -CAfile ../../build/certs/ca.pem ../../build/certs/client.crt
+}
+
+# MAIN
+make_ca
+make_int
+make_server
+make_client
diff --git a/scripts/mtls/server.cnf b/scripts/mtls/server.cnf
new file mode 100644
index 000000000..11408873d
--- /dev/null
+++ b/scripts/mtls/server.cnf
@@ -0,0 +1,27 @@
+[req]
+prompt = no
+default_bits = 4096
+x509_extensions = v3_req
+req_extensions = v3_req
+default_md = sha512
+distinguished_name = req_distinguished_name
+
+[req_distinguished_name]
+countryName = IE
+stateOrProvinceName = Munster
+localityName = Cork
+organizationName = NGINX, Inc.
+commonName = agent-server.example.com
+
+[v3_req]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
+extendedKeyUsage = critical, serverAuth
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = example.com
+DNS.2 = *.example.com
+DNS.3 = example.test
+IP.1 = 127.0.0.1
+IP.2 = ::1
diff --git a/src/plugins/agent_api.go b/src/plugins/agent_api.go
index 8fa383dab..25d4ed55c 100644
--- a/src/plugins/agent_api.go
+++ b/src/plugins/agent_api.go
@@ -70,6 +70,11 @@ func (a *AgentAPI) createHttpServer() {
a.nginxHandler = &NginxHandler{a.env, a.nginxBinary}
mux.Handle("/nginx/", a.nginxHandler)
+ // func(w http.ResponseWriter, req *http.Request) {
+ // w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
+ // w.Write([]byte("This is an example server.\n"))
+ //})
+
a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index a67485a4c..8f766c2e2 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -2,19 +2,25 @@ package plugins
import (
"context"
+ "crypto/tls"
"encoding/json"
"fmt"
+ "io/ioutil"
"net/http"
"net/http/httptest"
+ "os"
+ "time"
+
// "os/exec"
+
"testing"
+ "github.com/go-resty/resty/v2"
"github.com/nginx/agent/sdk/v2/proto"
"github.com/nginx/agent/v2/src/core"
"github.com/nginx/agent/v2/src/core/config"
tutils "github.com/nginx/agent/v2/test/utils"
"github.com/stretchr/testify/assert"
- "github.com/go-resty/resty/v2"
)
func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) {
@@ -84,24 +90,173 @@ func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) {
}
}
+// const (
+// GEN_CNF = "../../%s"
+// GEN_CERT = "../../%s"
+// )
+
func TestMtlsForApi(t *testing.T) {
- dir := t.TempDir()
- t.Logf("%v", dir)
- conf := &config.Config{
- AgentAPI: config.AgentAPI{
- Port: 2345,
+ tests := []struct {
+ name string
+ expected *proto.NginxDetails
+ dir string
+ conf *config.Config
+ clientMTLS bool
+ }{
+ {
+ name: "no tls test",
+ expected: tutils.GetDetailsMap()["12345"],
+ dir: t.TempDir(),
+ conf: &config.Config{
+ AgentAPI: config.AgentAPI{
+ Port: 2345,
+ Key: "",
+ Cert: "",
+ },
+ },
+ clientMTLS: false,
},
+ {
+ name: "mtls test",
+ expected: tutils.GetDetailsMap()["12345"],
+ dir: t.TempDir(),
+ conf: &config.Config{
+ AgentAPI: config.AgentAPI{
+ Port: 2345,
+ Key: "../../build/certs/server.key",
+ Cert: "../../build/certs/server.crt",
+ },
+ },
+ clientMTLS: true,
+ },
+ // {
+ // name: "mtls test, no client cert",
+ // expected: tutils.GetDetailsMap()["12345"],
+ // dir: t.TempDir(),
+ // conf: &config.Config{
+ // AgentAPI: config.AgentAPI{
+ // Port: 2345,
+ // Key: "../../build/certs/server/ca.key",
+ // Cert: "../../build/certs/server/ca.crt",
+ // },
+ // },
+ // clientMTLS: false,
+ // },
}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Logf("%v", tt.dir)
+
+ if (tt.conf.AgentAPI.Key != "") {
+ certsDir, err := os.MkdirTemp(tt.dir, "certs")
+ if err != nil {
+ t.Fail()
+ }
+ t.Logf("%s", certsDir)
+
+ // commands := []string{
+ // fmt.Sprintf("%s ca --cn 'client-ca.local' --state Cork --locality Cork --org NGINX --country IE --out %s/client/conf", GEN_CNF, certsDir),
+ // fmt.Sprintf("%s ca --config %s/client/conf/ca.cnf --out %s/client", GEN_CERT, certsDir, certsDir),
+ // fmt.Sprintf("cp %s/client/ca.crt %s/client-ca.crt", certsDir, certsDir),
+ // fmt.Sprintf("cp %s/client/ca.key %s/client-ca.key", certsDir, certsDir),
+
+ // fmt.Sprintf("%s ca --cn 'server-ca.local' --state Cork --locality Cork --org NGINX --country IE --out %s/server/conf", GEN_CNF, certsDir),
+ // fmt.Sprintf("%s ca --config %s/server/conf/ca.cnf --out %s/server", GEN_CERT, certsDir, certsDir),
+ // fmt.Sprintf("cp %s/server/ca.crt %s/server-ca.pem", certsDir, certsDir),
+ // fmt.Sprintf("cp %s/server/ca.key %s/server-ca.key", certsDir, certsDir),
+ // }
+
+ // for _, command := range commands {
+ // cmd := exec.Command(command)
+ // stdout, err := cmd.Output()
- // generateCertificate(t, dir)
- pluginUnderTest := NewAgentAPI(conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
- pluginUnderTest.Init(core.NewMockMessagePipe(context.TODO()))
+ // if err != nil {
+ // t.Fatal(err)
+ // }
+
+ // t.Log(string(stdout))
+ // }
- client := resty.New()
+ // openssl req -new -nodes -x509 -out certs/server.pem -keyout certs/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
+ // echo "make client cert"
+ // openssl req -new -nodes -x509 -out certs/client.pem -keyout certs/client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
+ }
- resp, err := client.R().EnableTrace().Get(fmt.Sprintf("http://localhost:%d/nginx", conf.AgentAPI.Port))
+
+ // generateCertificate(t, dir)
+ pluginUnderTest := NewAgentAPI(tt.conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
+ pluginUnderTest.Init(core.NewMockMessagePipe(context.TODO()))
+
+ client := resty.New()
- // Explore response object
+ client.SetDebug(true)
+ var url string
+ if (tt.conf.AgentAPI.Key != "") {
+ url = fmt.Sprintf("https://localhost:%d/nginx", tt.conf.AgentAPI.Port)
+
+ if (tt.clientMTLS) {
+ // Assign Client TLSClientConfig
+ // One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
+ crt, err := ioutil.ReadFile("../../build/certs/client.crt")
+ assert.NoError(t, err)
+ key, err := ioutil.ReadFile("../../build/certs/client.key")
+ assert.NoError(t, err)
+ tlsConfig := &tls.Config{
+ // ServerName: "localhost", // Optional
+ // MaxVersion: tls.VersionTLS13, // Optional
+ GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
+ if cert, err := tls.X509KeyPair(crt, key); err != nil {
+ return nil, err
+
+ } else {
+ return &cert, nil
+ }
+ },
+ }
+
+ transport := &http.Transport{ TLSClientConfig: tlsConfig }
+ client.SetTransport(transport)
+ // client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
+
+ // or One can disable security check (https)
+ //client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+ }
+ } else {
+ url = fmt.Sprintf("http://localhost:%d/nginx", tt.conf.AgentAPI.Port)
+ }
+ // Set client timeout as per your need
+ client.SetTimeout(1 * time.Minute)
+ client.AddRetryCondition(
+ // RetryConditionFunc type is for retry condition function
+ // input: non-nil Response OR request execution error
+ func(r *resty.Response, err error) bool {
+ return r.StatusCode() == http.StatusTooManyRequests
+ },
+ )
+
+ resp, err := client.R().EnableTrace().Get(url)
+
+ printResult(resp, err)
+
+ var details []*proto.NginxDetails
+ err = json.Unmarshal(resp.Body(), &details)
+
+ assert.NoError(t, err)
+
+ expected := tutils.GetDetailsMap()["12345"]
+ assert.Len(t, details, 1)
+ if (len(details) < 1) {
+ assert.Fail(t, "No data returned")
+ } else {
+ assert.Equal(t, expected, details[0])
+ }
+ pluginUnderTest.Close()
+ })
+ }
+}
+
+// explore response object for debugging
+func printResult( resp *resty.Response, err error) (*resty.Response, error) {
fmt.Println("Response Info:")
fmt.Println(" Error :", err)
fmt.Println(" Status Code:", resp.StatusCode())
@@ -111,19 +266,11 @@ func TestMtlsForApi(t *testing.T) {
fmt.Println(" Received At:", resp.ReceivedAt())
fmt.Println(" Body :\n", resp)
fmt.Println()
-
- var details []*proto.NginxDetails
- err = json.Unmarshal(resp.Body(),&details)
-
- assert.NoError(t, err)
- // var responseItems map[string]*proto.NginxDetails
- expected := tutils.GetDetailsMap()["12345"]
- assert.Len(t, details, 1)
- assert.Equal(t, expected, details[0])
+ return resp, err
}
// func generateCertificate(t *testing.T, dir string) error {
-// cmd := exec.Command("../../scripts/mtls/gen_cnf.sh", "ca", "--cn", "'ca.local'", "--state", "Cork", "--locality", "Cork", "--org", "NGINX", "--country", "IE", "--out", dir)
+// cmd := exec.Command("../../%s", "ca", "--cn", "'ca.local'", "--state", "Cork", "--locality", "Cork", "--org", "NGINX", "--country", "IE", "--out", dir)
// err := cmd.Run()
// if err != nil {
@@ -131,7 +278,7 @@ func TestMtlsForApi(t *testing.T) {
// t.Fail()
// }
-// cmd1 := exec.Command("../../scripts/mtls/gen_cert.sh", "ca", "--config", "certs/conf/ca.cnf", "--out", dir)
+// cmd1 := exec.Command("../../%s", "ca", "--config", "certs/conf/ca.cnf", "--out", dir)
// err = cmd1.Run()
// if err != nil {
@@ -139,8 +286,8 @@ func TestMtlsForApi(t *testing.T) {
// t.Fail()
// }
-// // scripts/mtls/gen_cnf.sh ca --cn '${CERT_CLIENT_CA_CN}' --state Cork --locality Cork --org NGINX --country IE --out ${CERTS_DIR}/client/conf
-// // scripts/mtls/gen_cert.sh ca --config ${CERTS_DIR}/client/conf/ca.cnf --out ${CERTS_DIR}/client
+// // %s ca --cn '${CERT_CLIENT_CA_CN}' --state Cork --locality Cork --org NGINX --country IE --out %s/client/conf
+// // %s ca --config %s/client/conf/ca.cnf --out %s/client
// return nil
// }
From 56dc9973bbe0d863f1f2b3bff3c3f2022d46a97d Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Fri, 25 Nov 2022 16:36:51 +0000
Subject: [PATCH 06/21] works except test
---
scripts/mtls/ca-intermediate.cnf | 2 +-
scripts/mtls/ca.cnf | 2 +-
scripts/mtls/make_certs.sh | 8 ++++----
scripts/mtls/server.cnf | 5 +----
src/plugins/agent_api_test.go | 25 +------------------------
5 files changed, 8 insertions(+), 34 deletions(-)
diff --git a/scripts/mtls/ca-intermediate.cnf b/scripts/mtls/ca-intermediate.cnf
index 6a0548c1b..1ad18cee3 100644
--- a/scripts/mtls/ca-intermediate.cnf
+++ b/scripts/mtls/ca-intermediate.cnf
@@ -10,7 +10,7 @@ countryName = IE
stateOrProvinceName = Munster
localityName = Cork
organizationName = NGINX, Inc.
-commonName = agent-int-ca
+commonName = agent-server-int.example.com
[v3_req]
basicConstraints = critical, CA:true
diff --git a/scripts/mtls/ca.cnf b/scripts/mtls/ca.cnf
index e6fb703e6..03e165040 100644
--- a/scripts/mtls/ca.cnf
+++ b/scripts/mtls/ca.cnf
@@ -10,7 +10,7 @@ countryName = IE
stateOrProvinceName = Munster
localityName = Cork
organizationName = NGINX, Inc.
-commonName = agent-int-ca
+commonName = agent-ca
[v3_req]
basicConstraints = critical, CA:true
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index f5ad126a5..b8901e838 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -8,7 +8,7 @@ fi
make_ca() {
echo "Creating Self-Signed Root CA certificate and key"
openssl req \
- -newkey rsa:1024 \
+ -new -newkey rsa:4096 \
-nodes \
-x509 \
-keyout ../../build/certs/ca.key \
@@ -21,7 +21,7 @@ make_ca() {
make_int() {
echo "Creating Intermediate CA certificate and key"
openssl req \
- -newkey rsa:1024 \
+ -new -newkey rsa:4096 \
-nodes \
-keyout ../../build/certs/ca_int.key \
-out ../../build/certs/ca_int.csr \
@@ -46,7 +46,7 @@ make_int() {
make_server() {
echo "Creating nginx-manger certificate and key"
openssl req \
- -newkey rsa:1024 \
+ -new -newkey rsa:4096 \
-nodes \
-keyout ../../build/certs/server.key \
-out ../../build/certs/server.csr \
@@ -68,7 +68,7 @@ make_server() {
make_client() {
echo "Creating Client certificate and key"
openssl req \
- -newkey rsa:1024 \
+ -new -newkey rsa:4096 \
-nodes \
-keyout ../../build/certs/client.key \
-out ../../build/certs/client.csr \
diff --git a/scripts/mtls/server.cnf b/scripts/mtls/server.cnf
index 11408873d..b5eef0820 100644
--- a/scripts/mtls/server.cnf
+++ b/scripts/mtls/server.cnf
@@ -11,7 +11,7 @@ countryName = IE
stateOrProvinceName = Munster
localityName = Cork
organizationName = NGINX, Inc.
-commonName = agent-server.example.com
+commonName = server.example.com
[v3_req]
basicConstraints = CA:FALSE
@@ -21,7 +21,4 @@ subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
-DNS.2 = *.example.com
-DNS.3 = example.test
IP.1 = 127.0.0.1
-IP.2 = ::1
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index 8f766c2e2..76c1b823b 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -203,7 +203,7 @@ func TestMtlsForApi(t *testing.T) {
assert.NoError(t, err)
tlsConfig := &tls.Config{
// ServerName: "localhost", // Optional
- // MaxVersion: tls.VersionTLS13, // Optional
+ MaxVersion: tls.VersionTLS13, // Optional
GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
if cert, err := tls.X509KeyPair(crt, key); err != nil {
return nil, err
@@ -268,26 +268,3 @@ func printResult( resp *resty.Response, err error) (*resty.Response, error) {
fmt.Println()
return resp, err
}
-
-// func generateCertificate(t *testing.T, dir string) error {
-// cmd := exec.Command("../../%s", "ca", "--cn", "'ca.local'", "--state", "Cork", "--locality", "Cork", "--org", "NGINX", "--country", "IE", "--out", dir)
-
-// err := cmd.Run()
-// if err != nil {
-// t.Logf("%v", err)
-// t.Fail()
-// }
-
-// cmd1 := exec.Command("../../%s", "ca", "--config", "certs/conf/ca.cnf", "--out", dir)
-
-// err = cmd1.Run()
-// if err != nil {
-// t.Logf("%v", err)
-// t.Fail()
-// }
-
-// // %s ca --cn '${CERT_CLIENT_CA_CN}' --state Cork --locality Cork --org NGINX --country IE --out %s/client/conf
-// // %s ca --config %s/client/conf/ca.cnf --out %s/client
-
-// return nil
-// }
From ce092d7ddc82d46253edbdad89f02a6dacf615b5 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Fri, 25 Nov 2022 17:23:11 +0000
Subject: [PATCH 07/21] wip tests
---
main.go | 6 +-
scripts/mtls/make_certs.sh | 4 +
sdk/proto/events/event.pb.go | 1 +
src/core/config/defaults.go | 12 +-
src/core/config/types.go | 2 +-
src/plugins/agent_api.go | 10 +-
src/plugins/agent_api_test.go | 114 +++++++++---------
src/plugins/events_test.go | 18 +--
.../agent/sdk/v2/proto/events/event.pb.go | 1 +
.../agent/v2/src/core/config/defaults.go | 12 +-
.../nginx/agent/v2/src/core/config/types.go | 2 +-
.../nginx/agent/v2/src/plugins/agent_api.go | 9 +-
.../agent/sdk/v2/proto/events/event.pb.go | 1 +
13 files changed, 104 insertions(+), 88 deletions(-)
diff --git a/main.go b/main.go
index df6a794f8..4b3dc0817 100644
--- a/main.go
+++ b/main.go
@@ -70,7 +70,7 @@ func main() {
sdkGRPC.InitMeta(loadedConfig.ClientID, loadedConfig.CloudAccountID)
controller, commander, reporter := createGrpcClients(ctx, loadedConfig)
-
+
if controller != nil {
if err := controller.Connect(); err != nil {
log.Warnf("Unable to connect to control plane: %v", err)
@@ -135,7 +135,7 @@ func handleSignals(
}
func connectionUnavilable(loadedConfig *config.Config) bool {
- return loadedConfig.Server.Host == "" || loadedConfig.Server.GrpcPort == 0
+ return loadedConfig.Server.Host == "" || loadedConfig.Server.GrpcPort == 0
}
func createGrpcClients(ctx context.Context, loadedConfig *config.Config) (client.Controller, client.Commander, client.MetricReporter) {
@@ -143,7 +143,7 @@ func createGrpcClients(ctx context.Context, loadedConfig *config.Config) (client
log.Infof("GRPC clients not created")
return nil, nil, nil
}
-
+
grpcDialOptions := setDialOptions(loadedConfig)
secureMetricsDialOpts, err := sdkGRPC.SecureDialOptions(
loadedConfig.TLS.Enable,
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index b8901e838..abdf9f268 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -11,6 +11,7 @@ make_ca() {
-new -newkey rsa:4096 \
-nodes \
-x509 \
+ -sha256 \
-keyout ../../build/certs/ca.key \
-out ../../build/certs/ca.crt \
-config ca.cnf \
@@ -30,6 +31,7 @@ make_int() {
openssl req -in ../../build/certs/ca_int.csr -noout -verify
openssl x509 \
-req \
+ -sha256 \
-CA ../../build/certs/ca.crt \
-CAkey ../../build/certs/ca.key \
-CAcreateserial \
@@ -54,6 +56,7 @@ make_server() {
openssl req -in ../../build/certs/server.csr -noout -verify
openssl x509 \
-req \
+ -sha256 \
-CA ../../build/certs/ca_int.crt \
-CAkey ../../build/certs/ca_int.key \
-CAcreateserial \
@@ -76,6 +79,7 @@ make_client() {
openssl req -in ../../build/certs/client.csr -noout -verify
openssl x509 \
-req \
+ -sha256 \
-CA ../../build/certs/ca.crt \
-CAkey ../../build/certs/ca.key \
-CAcreateserial \
diff --git a/sdk/proto/events/event.pb.go b/sdk/proto/events/event.pb.go
index 0d8ac2fec..903408281 100644
--- a/sdk/proto/events/event.pb.go
+++ b/sdk/proto/events/event.pb.go
@@ -123,6 +123,7 @@ func (m *Metadata) GetCategory() string {
type Event struct {
Metadata *Metadata `protobuf:"bytes,1,opt,name=Metadata,proto3" json:"metadata"`
// Types that are valid to be assigned to Data:
+ //
// *Event_ActivityEvent
// *Event_SecurityViolationEvent
Data isEvent_Data `protobuf_oneof:"data"`
diff --git a/src/core/config/defaults.go b/src/core/config/defaults.go
index 5429efe18..f0750f2f5 100644
--- a/src/core/config/defaults.go
+++ b/src/core/config/defaults.go
@@ -37,8 +37,8 @@ var (
Path: "/var/log/nginx-agent",
},
Server: Server{
- Command: "",
- Metrics: "",
+ Command: "",
+ Metrics: "",
// token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable
// so setting to random uuid at the moment, tls connection won't work without the auth header
Token: uuid.New().String(),
@@ -206,12 +206,12 @@ var (
DefaultValue: Defaults.Log.Path,
},
&StringFlag{
- Name: ServerHost,
- Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.",
+ Name: ServerHost,
+ Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.",
},
&IntFlag{
- Name: ServerGrpcPort,
- Usage: "The desired GRPC port to use for nginx-agent traffic.",
+ Name: ServerGrpcPort,
+ Usage: "The desired GRPC port to use for nginx-agent traffic.",
},
&StringFlag{
Name: ServerToken,
diff --git a/src/core/config/types.go b/src/core/config/types.go
index f47145ae5..25969702c 100644
--- a/src/core/config/types.go
+++ b/src/core/config/types.go
@@ -42,7 +42,7 @@ type Server struct {
}
type AgentAPI struct {
- Port int `mapstructure:"port" yaml:"-"`
+ Port int `mapstructure:"port" yaml:"-"`
Cert string `mapstructure:"cert" yaml:"-"`
Key string `mapstructure:"key" yaml:"-"`
}
diff --git a/src/plugins/agent_api.go b/src/plugins/agent_api.go
index 25d4ed55c..40fc8afee 100644
--- a/src/plugins/agent_api.go
+++ b/src/plugins/agent_api.go
@@ -71,21 +71,21 @@ func (a *AgentAPI) createHttpServer() {
mux.Handle("/nginx/", a.nginxHandler)
// func(w http.ResponseWriter, req *http.Request) {
- // w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
- // w.Write([]byte("This is an example server.\n"))
- //})
+ // w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
+ // w.Write([]byte("This is an example server.\n"))
+ //})
a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
}
- if (a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" && a.config.AgentAPI.Port != 0) {
+ if a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" && a.config.AgentAPI.Port != 0 {
log.Info("Starting Agent API HTTP server with cert and key and port from config")
if err := a.server.ListenAndServeTLS(a.config.AgentAPI.Cert, a.config.AgentAPI.Key); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
}
- } else if (a.config.AgentAPI.Port != 0) {
+ } else if a.config.AgentAPI.Port != 0 {
log.Info("Starting Agent API HTTP server with port from config and TLS disabled")
if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index 76c1b823b..0f1c0bc1f 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -3,6 +3,7 @@ package plugins
import (
"context"
"crypto/tls"
+ "crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
@@ -90,11 +91,6 @@ func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) {
}
}
-// const (
-// GEN_CNF = "../../%s"
-// GEN_CERT = "../../%s"
-// )
-
func TestMtlsForApi(t *testing.T) {
tests := []struct {
name string
@@ -104,11 +100,11 @@ func TestMtlsForApi(t *testing.T) {
clientMTLS bool
}{
{
- name: "no tls test",
+ name: "no tls test",
expected: tutils.GetDetailsMap()["12345"],
dir: t.TempDir(),
- conf: &config.Config{
- AgentAPI: config.AgentAPI{
+ conf: &config.Config{
+ AgentAPI: config.AgentAPI{
Port: 2345,
Key: "",
Cert: "",
@@ -117,11 +113,11 @@ func TestMtlsForApi(t *testing.T) {
clientMTLS: false,
},
{
- name: "mtls test",
+ name: "mtls test",
expected: tutils.GetDetailsMap()["12345"],
dir: t.TempDir(),
- conf: &config.Config{
- AgentAPI: config.AgentAPI{
+ conf: &config.Config{
+ AgentAPI: config.AgentAPI{
Port: 2345,
Key: "../../build/certs/server.key",
Cert: "../../build/certs/server.crt",
@@ -134,20 +130,20 @@ func TestMtlsForApi(t *testing.T) {
// expected: tutils.GetDetailsMap()["12345"],
// dir: t.TempDir(),
// conf: &config.Config{
- // AgentAPI: config.AgentAPI{
+ // AgentAPI: config.AgentAPI{
// Port: 2345,
- // Key: "../../build/certs/server/ca.key",
- // Cert: "../../build/certs/server/ca.crt",
+ // Key: "../../build/certs/server.key",
+ // Cert: "../../build/certs/server.crt",
// },
// },
// clientMTLS: false,
- // },
+ // },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Logf("%v", tt.dir)
- if (tt.conf.AgentAPI.Key != "") {
+ if tt.conf.AgentAPI.Key != "" {
certsDir, err := os.MkdirTemp(tt.dir, "certs")
if err != nil {
t.Fail()
@@ -165,7 +161,7 @@ func TestMtlsForApi(t *testing.T) {
// fmt.Sprintf("cp %s/server/ca.crt %s/server-ca.pem", certsDir, certsDir),
// fmt.Sprintf("cp %s/server/ca.key %s/server-ca.key", certsDir, certsDir),
// }
-
+
// for _, command := range commands {
// cmd := exec.Command(command)
// stdout, err := cmd.Output()
@@ -173,53 +169,28 @@ func TestMtlsForApi(t *testing.T) {
// if err != nil {
// t.Fatal(err)
// }
-
+
// t.Log(string(stdout))
- // }
+ // }
// openssl req -new -nodes -x509 -out certs/server.pem -keyout certs/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
// echo "make client cert"
// openssl req -new -nodes -x509 -out certs/client.pem -keyout certs/client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
}
-
- // generateCertificate(t, dir)
pluginUnderTest := NewAgentAPI(tt.conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
pluginUnderTest.Init(core.NewMockMessagePipe(context.TODO()))
-
+
client := resty.New()
client.SetDebug(true)
var url string
- if (tt.conf.AgentAPI.Key != "") {
- url = fmt.Sprintf("https://localhost:%d/nginx", tt.conf.AgentAPI.Port)
-
- if (tt.clientMTLS) {
- // Assign Client TLSClientConfig
- // One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
- crt, err := ioutil.ReadFile("../../build/certs/client.crt")
- assert.NoError(t, err)
- key, err := ioutil.ReadFile("../../build/certs/client.key")
- assert.NoError(t, err)
- tlsConfig := &tls.Config{
- // ServerName: "localhost", // Optional
- MaxVersion: tls.VersionTLS13, // Optional
- GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
- if cert, err := tls.X509KeyPair(crt, key); err != nil {
- return nil, err
-
- } else {
- return &cert, nil
- }
- },
- }
-
- transport := &http.Transport{ TLSClientConfig: tlsConfig }
- client.SetTransport(transport)
- // client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
+ if tt.conf.AgentAPI.Key != "" {
+ url = fmt.Sprintf("https://127.0.0.1:%d/nginx", tt.conf.AgentAPI.Port)
- // or One can disable security check (https)
- //client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+ if tt.clientMTLS {
+ transport := &http.Transport{TLSClientConfig: getConfig(t)}
+ client.SetTransport(transport)
}
} else {
url = fmt.Sprintf("http://localhost:%d/nginx", tt.conf.AgentAPI.Port)
@@ -235,28 +206,61 @@ func TestMtlsForApi(t *testing.T) {
)
resp, err := client.R().EnableTrace().Get(url)
-
+
printResult(resp, err)
-
+
var details []*proto.NginxDetails
err = json.Unmarshal(resp.Body(), &details)
-
+
assert.NoError(t, err)
expected := tutils.GetDetailsMap()["12345"]
assert.Len(t, details, 1)
- if (len(details) < 1) {
+ if len(details) < 1 {
assert.Fail(t, "No data returned")
} else {
assert.Equal(t, expected, details[0])
}
+
pluginUnderTest.Close()
})
}
}
+func getConfig(t *testing.T) *tls.Config {
+ crt, err := ioutil.ReadFile("../../build/certs/client.crt")
+ assert.NoError(t, err)
+ key, err := ioutil.ReadFile("../../build/certs/client.key")
+ assert.NoError(t, err)
+ ca, err := ioutil.ReadFile("../../build/certs/ca.pem")
+ assert.NoError(t, err)
+
+ os.Setenv("SSL_CERT_FILE", "../../build/certs/ca.pem")
+
+ cert, err := tls.X509KeyPair(crt, key)
+ if err != nil {
+ assert.Fail(t, "error reading cert")
+
+ }
+ tlsConfig := &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ }
+
+ caPool := tlsConfig.RootCAs
+ if caPool == nil {
+ caPool = x509.NewCertPool()
+ }
+
+ if !caPool.AppendCertsFromPEM(ca) {
+ assert.Fail(t, "Can't append cert")
+ }
+
+ tlsConfig.RootCAs = caPool
+ return tlsConfig
+}
+
// explore response object for debugging
-func printResult( resp *resty.Response, err error) (*resty.Response, error) {
+func printResult(resp *resty.Response, err error) (*resty.Response, error) {
fmt.Println("Response Info:")
fmt.Println(" Error :", err)
fmt.Println(" Status Code:", resp.StatusCode())
diff --git a/src/plugins/events_test.go b/src/plugins/events_test.go
index e127833b4..6cd3662b7 100644
--- a/src/plugins/events_test.go
+++ b/src/plugins/events_test.go
@@ -52,7 +52,7 @@ func TestActivityEvents_Process(t *testing.T) {
expectedEventReport *eventsProto.EventReport
}{
{
- name: "test NginxInstancesFound message",
+ name: "test NginxInstancesFound message",
message: core.NewMessage(core.NginxInstancesFound, tutils.GetDetailsMap()),
msgTopics: []string{
core.NginxInstancesFound,
@@ -81,8 +81,8 @@ func TestActivityEvents_Process(t *testing.T) {
{
name: "test NginxReloadComplete message - reload failed",
message: core.NewMessage(core.NginxReloadComplete, NginxReloadResponse{
- succeeded: false,
- nginxDetails: tutils.GetDetailsMap()["12345"],
+ succeeded: false,
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
@@ -111,8 +111,8 @@ func TestActivityEvents_Process(t *testing.T) {
{
name: "test NginxReloadComplete message - reload succeeded",
message: core.NewMessage(core.NginxReloadComplete, NginxReloadResponse{
- succeeded: true,
- nginxDetails: tutils.GetDetailsMap()["12345"],
+ succeeded: true,
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
@@ -232,8 +232,8 @@ func TestActivityEvents_Process(t *testing.T) {
{
name: "test successful ConfigRollbackResponse message",
message: core.NewMessage(core.ConfigRollbackResponse, ConfigRollbackResponse{
- succeeded: true,
- nginxDetails: tutils.GetDetailsMap()["12345"],
+ succeeded: true,
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
@@ -262,8 +262,8 @@ func TestActivityEvents_Process(t *testing.T) {
{
name: "test failed ConfigRollbackResponse message",
message: core.NewMessage(core.ConfigRollbackResponse, ConfigRollbackResponse{
- succeeded: false,
- nginxDetails: tutils.GetDetailsMap()["12345"],
+ succeeded: false,
+ nginxDetails: tutils.GetDetailsMap()["12345"],
correlationId: uuid.NewString(),
}),
msgTopics: []string{
diff --git a/test/performance/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go b/test/performance/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go
index 0d8ac2fec..903408281 100644
--- a/test/performance/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go
+++ b/test/performance/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go
@@ -123,6 +123,7 @@ func (m *Metadata) GetCategory() string {
type Event struct {
Metadata *Metadata `protobuf:"bytes,1,opt,name=Metadata,proto3" json:"metadata"`
// Types that are valid to be assigned to Data:
+ //
// *Event_ActivityEvent
// *Event_SecurityViolationEvent
Data isEvent_Data `protobuf_oneof:"data"`
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
index 5429efe18..f0750f2f5 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
@@ -37,8 +37,8 @@ var (
Path: "/var/log/nginx-agent",
},
Server: Server{
- Command: "",
- Metrics: "",
+ Command: "",
+ Metrics: "",
// token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable
// so setting to random uuid at the moment, tls connection won't work without the auth header
Token: uuid.New().String(),
@@ -206,12 +206,12 @@ var (
DefaultValue: Defaults.Log.Path,
},
&StringFlag{
- Name: ServerHost,
- Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.",
+ Name: ServerHost,
+ Usage: "The IP address of the server host. IPv4 addresses and hostnames are supported.",
},
&IntFlag{
- Name: ServerGrpcPort,
- Usage: "The desired GRPC port to use for nginx-agent traffic.",
+ Name: ServerGrpcPort,
+ Usage: "The desired GRPC port to use for nginx-agent traffic.",
},
&StringFlag{
Name: ServerToken,
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go
index f47145ae5..25969702c 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go
@@ -42,7 +42,7 @@ type Server struct {
}
type AgentAPI struct {
- Port int `mapstructure:"port" yaml:"-"`
+ Port int `mapstructure:"port" yaml:"-"`
Cert string `mapstructure:"cert" yaml:"-"`
Key string `mapstructure:"key" yaml:"-"`
}
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
index 8fa383dab..40fc8afee 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
@@ -70,17 +70,22 @@ func (a *AgentAPI) createHttpServer() {
a.nginxHandler = &NginxHandler{a.env, a.nginxBinary}
mux.Handle("/nginx/", a.nginxHandler)
+ // func(w http.ResponseWriter, req *http.Request) {
+ // w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
+ // w.Write([]byte("This is an example server.\n"))
+ //})
+
a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
}
- if (a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" && a.config.AgentAPI.Port != 0) {
+ if a.config.AgentAPI.Cert != "" && a.config.AgentAPI.Key != "" && a.config.AgentAPI.Port != 0 {
log.Info("Starting Agent API HTTP server with cert and key and port from config")
if err := a.server.ListenAndServeTLS(a.config.AgentAPI.Cert, a.config.AgentAPI.Key); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
}
- } else if (a.config.AgentAPI.Port != 0) {
+ } else if a.config.AgentAPI.Port != 0 {
log.Info("Starting Agent API HTTP server with port from config and TLS disabled")
if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
diff --git a/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go b/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go
index 0d8ac2fec..903408281 100644
--- a/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go
+++ b/vendor/github.com/nginx/agent/sdk/v2/proto/events/event.pb.go
@@ -123,6 +123,7 @@ func (m *Metadata) GetCategory() string {
type Event struct {
Metadata *Metadata `protobuf:"bytes,1,opt,name=Metadata,proto3" json:"metadata"`
// Types that are valid to be assigned to Data:
+ //
// *Event_ActivityEvent
// *Event_SecurityViolationEvent
Data isEvent_Data `protobuf_oneof:"data"`
From 0de30bd7eece9f55f2f11001329f0c1ea985be1f Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Fri, 25 Nov 2022 17:29:54 +0000
Subject: [PATCH 08/21] tidy test
---
src/plugins/agent_api_test.go | 44 +++++++----------------------------
1 file changed, 9 insertions(+), 35 deletions(-)
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index 0f1c0bc1f..91b35d94a 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -143,39 +143,19 @@ func TestMtlsForApi(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Logf("%v", tt.dir)
+ var url string
+
if tt.conf.AgentAPI.Key != "" {
+ url = fmt.Sprintf("https://127.0.0.1:%d/nginx", tt.conf.AgentAPI.Port)
+
certsDir, err := os.MkdirTemp(tt.dir, "certs")
if err != nil {
t.Fail()
}
t.Logf("%s", certsDir)
+ } else {
+ url = fmt.Sprintf("http://localhost:%d/nginx", tt.conf.AgentAPI.Port)
- // commands := []string{
- // fmt.Sprintf("%s ca --cn 'client-ca.local' --state Cork --locality Cork --org NGINX --country IE --out %s/client/conf", GEN_CNF, certsDir),
- // fmt.Sprintf("%s ca --config %s/client/conf/ca.cnf --out %s/client", GEN_CERT, certsDir, certsDir),
- // fmt.Sprintf("cp %s/client/ca.crt %s/client-ca.crt", certsDir, certsDir),
- // fmt.Sprintf("cp %s/client/ca.key %s/client-ca.key", certsDir, certsDir),
-
- // fmt.Sprintf("%s ca --cn 'server-ca.local' --state Cork --locality Cork --org NGINX --country IE --out %s/server/conf", GEN_CNF, certsDir),
- // fmt.Sprintf("%s ca --config %s/server/conf/ca.cnf --out %s/server", GEN_CERT, certsDir, certsDir),
- // fmt.Sprintf("cp %s/server/ca.crt %s/server-ca.pem", certsDir, certsDir),
- // fmt.Sprintf("cp %s/server/ca.key %s/server-ca.key", certsDir, certsDir),
- // }
-
- // for _, command := range commands {
- // cmd := exec.Command(command)
- // stdout, err := cmd.Output()
-
- // if err != nil {
- // t.Fatal(err)
- // }
-
- // t.Log(string(stdout))
- // }
-
- // openssl req -new -nodes -x509 -out certs/server.pem -keyout certs/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
- // echo "make client cert"
- // openssl req -new -nodes -x509 -out certs/client.pem -keyout certs/client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
}
pluginUnderTest := NewAgentAPI(tt.conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
@@ -184,16 +164,10 @@ func TestMtlsForApi(t *testing.T) {
client := resty.New()
client.SetDebug(true)
- var url string
- if tt.conf.AgentAPI.Key != "" {
- url = fmt.Sprintf("https://127.0.0.1:%d/nginx", tt.conf.AgentAPI.Port)
- if tt.clientMTLS {
- transport := &http.Transport{TLSClientConfig: getConfig(t)}
- client.SetTransport(transport)
- }
- } else {
- url = fmt.Sprintf("http://localhost:%d/nginx", tt.conf.AgentAPI.Port)
+ if tt.clientMTLS {
+ transport := &http.Transport{TLSClientConfig: getConfig(t)}
+ client.SetTransport(transport)
}
// Set client timeout as per your need
client.SetTimeout(1 * time.Minute)
From 3f4e628f790d1b963086f1720339d59c0504c445 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 11:44:34 +0000
Subject: [PATCH 09/21] wip
---
Makefile | 3 +++
scripts/mtls/make_certs.sh | 9 +++++++--
src/plugins/agent_api_test.go | 36 +++++++++++++++++++++--------------
3 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/Makefile b/Makefile
index 35a971aa8..aebfeb0eb 100644
--- a/Makefile
+++ b/Makefile
@@ -198,6 +198,9 @@ certs: ## Generate TLS certificates
cp ${CERTS_DIR}/server/ee.crt ${CERTS_DIR}/server.crt
cp ${CERTS_DIR}/server/ee.key ${CERTS_DIR}/server.key
+certs-new: ## Generate TLS certificates
+ scripts/mtls/make_certs.sh
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Docker Helper Targets #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index abdf9f268..6362553a9 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -1,7 +1,12 @@
-#!/bin/bash
+#!/bin/sh
set -e
-if [ ! -d ../../build ]; then
+if [ -f .srl ]; then
+ rm .srl
+ echo ".srl removed"
+fi
+
+if [ ! -d ../../build/certs ]; then
mkdir -p ../../build/certs;
fi
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index 91b35d94a..55c8dae72 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -10,9 +10,7 @@ import (
"net/http"
"net/http/httptest"
"os"
- "time"
-
- // "os/exec"
+ "os/exec"
"testing"
@@ -169,18 +167,11 @@ func TestMtlsForApi(t *testing.T) {
transport := &http.Transport{TLSClientConfig: getConfig(t)}
client.SetTransport(transport)
}
- // Set client timeout as per your need
- client.SetTimeout(1 * time.Minute)
- client.AddRetryCondition(
- // RetryConditionFunc type is for retry condition function
- // input: non-nil Response OR request execution error
- func(r *resty.Response, err error) bool {
- return r.StatusCode() == http.StatusTooManyRequests
- },
- )
resp, err := client.R().EnableTrace().Get(url)
+ assert.NoError(t, err)
+
printResult(resp, err)
var details []*proto.NginxDetails
@@ -197,11 +188,28 @@ func TestMtlsForApi(t *testing.T) {
}
pluginUnderTest.Close()
+ if tt.clientMTLS {
+ os.RemoveAll("../../build")
+ }
})
}
}
func getConfig(t *testing.T) *tls.Config {
+ err := os.MkdirAll("../../build/certs", 0755)
+ if err != nil {
+ t.Logf("%v", err)
+ t.Fail()
+ }
+
+ cmd := exec.Command("../../scripts/mtls/make_certs.sh")
+
+ err = cmd.Run()
+ if err != nil {
+ t.Logf("%v", err)
+ t.Fail()
+ }
+
crt, err := ioutil.ReadFile("../../build/certs/client.crt")
assert.NoError(t, err)
key, err := ioutil.ReadFile("../../build/certs/client.key")
@@ -234,7 +242,7 @@ func getConfig(t *testing.T) *tls.Config {
}
// explore response object for debugging
-func printResult(resp *resty.Response, err error) (*resty.Response, error) {
+func printResult(resp *resty.Response, err error) (*resty.Response) {
fmt.Println("Response Info:")
fmt.Println(" Error :", err)
fmt.Println(" Status Code:", resp.StatusCode())
@@ -244,5 +252,5 @@ func printResult(resp *resty.Response, err error) (*resty.Response, error) {
fmt.Println(" Received At:", resp.ReceivedAt())
fmt.Println(" Body :\n", resp)
fmt.Println()
- return resp, err
+ return resp
}
From dcab1032dedb86706c9c7709df50edae250af6d9 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 13:06:22 +0000
Subject: [PATCH 10/21] added mtls test
---
scripts/mtls/make_certs.sh | 80 +++++++++++++++++++----------------
src/plugins/agent_api_test.go | 54 ++++++-----------------
2 files changed, 56 insertions(+), 78 deletions(-)
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index 6362553a9..a932cc921 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -1,13 +1,20 @@
#!/bin/sh
set -e
+scripts_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" 2>&1 && pwd )
+echo "$scripts_dir"
+
+build_dir="$scripts_dir/../../build/certs"
+echo "$build_dir"
+
if [ -f .srl ]; then
rm .srl
echo ".srl removed"
fi
-if [ ! -d ../../build/certs ]; then
- mkdir -p ../../build/certs;
+if [ ! -d "$build_dir" ]; then
+ echo "creating certs directory"
+ mkdir -p "$build_dir";
fi
make_ca() {
@@ -17,9 +24,9 @@ make_ca() {
-nodes \
-x509 \
-sha256 \
- -keyout ../../build/certs/ca.key \
- -out ../../build/certs/ca.crt \
- -config ca.cnf \
+ -keyout "$build_dir"/ca.key \
+ -out "$build_dir"/ca.crt \
+ -config "$scripts_dir"/ca.cnf \
-extensions v3_req \
-days 1
}
@@ -29,25 +36,25 @@ make_int() {
openssl req \
-new -newkey rsa:4096 \
-nodes \
- -keyout ../../build/certs/ca_int.key \
- -out ../../build/certs/ca_int.csr \
- -config ca-intermediate.cnf \
+ -keyout "$build_dir"/ca_int.key \
+ -out "$build_dir"/ca_int.csr \
+ -config "$scripts_dir"/ca-intermediate.cnf \
-extensions v3_req
- openssl req -in ../../build/certs/ca_int.csr -noout -verify
+ openssl req -in "$build_dir"/ca_int.csr -noout -verify
openssl x509 \
-req \
-sha256 \
- -CA ../../build/certs/ca.crt \
- -CAkey ../../build/certs/ca.key \
+ -CA "$build_dir"/ca.crt \
+ -CAkey "$build_dir"/ca.key \
-CAcreateserial \
- -in ../../build/certs/ca_int.csr \
- -out ../../build/certs/ca_int.crt \
- -extfile ca-intermediate.cnf \
+ -in "$build_dir"/ca_int.csr \
+ -out "$build_dir"/ca_int.crt \
+ -extfile "$scripts_dir"/ca-intermediate.cnf \
-extensions v3_req \
-days 1
- openssl verify -CAfile ../../build/certs/ca.crt ../../build/certs/ca_int.crt
+ openssl verify -CAfile "$build_dir"/ca.crt "$build_dir"/ca_int.crt
echo "Creating CA chain"
- cat ../../build/certs/ca_int.crt ../../build/certs/ca.crt > ../../build/certs/ca.pem
+ cat "$build_dir"/ca_int.crt "$build_dir"/ca.crt > "$build_dir"/ca.pem
}
make_server() {
@@ -55,22 +62,22 @@ make_server() {
openssl req \
-new -newkey rsa:4096 \
-nodes \
- -keyout ../../build/certs/server.key \
- -out ../../build/certs/server.csr \
- -config server.cnf
- openssl req -in ../../build/certs/server.csr -noout -verify
+ -keyout "$build_dir"/server.key \
+ -out "$build_dir"/server.csr \
+ -config "$scripts_dir"/server.cnf
+ openssl req -in "$build_dir"/server.csr -noout -verify
openssl x509 \
-req \
-sha256 \
- -CA ../../build/certs/ca_int.crt \
- -CAkey ../../build/certs/ca_int.key \
+ -CA "$build_dir"/ca_int.crt \
+ -CAkey "$build_dir"/ca_int.key \
-CAcreateserial \
- -in ../../build/certs/server.csr \
- -out ../../build/certs/server.crt \
- -extfile server.cnf \
+ -in "$build_dir"/server.csr \
+ -out "$build_dir"/server.crt \
+ -extfile "$scripts_dir"/server.cnf \
-extensions v3_req \
-days 1
- openssl verify -CAfile ../../build/certs/ca.pem ../../build/certs/server.crt
+ openssl verify -CAfile "$build_dir"/ca.pem "$build_dir"/server.crt
}
make_client() {
@@ -78,25 +85,26 @@ make_client() {
openssl req \
-new -newkey rsa:4096 \
-nodes \
- -keyout ../../build/certs/client.key \
- -out ../../build/certs/client.csr \
- -config client.cnf
- openssl req -in ../../build/certs/client.csr -noout -verify
+ -keyout "$build_dir"/client.key \
+ -out "$build_dir"/client.csr \
+ -config "$scripts_dir"/client.cnf
+ openssl req -in "$build_dir"/client.csr -noout -verify
openssl x509 \
-req \
-sha256 \
- -CA ../../build/certs/ca.crt \
- -CAkey ../../build/certs/ca.key \
+ -CA "$build_dir"/ca.crt \
+ -CAkey "$build_dir"/ca.key \
-CAcreateserial \
- -in ../../build/certs/client.csr \
- -out ../../build/certs/client.crt \
- -extfile client.cnf \
+ -in "$build_dir"/client.csr \
+ -out "$build_dir"/client.crt \
+ -extfile "$scripts_dir"/client.cnf \
-extensions v3_req \
-days 1
- openssl verify -CAfile ../../build/certs/ca.pem ../../build/certs/client.crt
+ openssl verify -CAfile "$build_dir"/ca.pem "$build_dir"/client.crt
}
# MAIN
+cd "$scripts_dir"
make_ca
make_int
make_server
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index 3b7023542..c5a25d2ed 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -123,7 +123,6 @@ func TestMtlsForApi(t *testing.T) {
{
name: "no tls test",
expected: tutils.GetDetailsMap()["12345"],
- dir: t.TempDir(),
conf: &config.Config{
AgentAPI: config.AgentAPI{
Port: 2345,
@@ -136,7 +135,6 @@ func TestMtlsForApi(t *testing.T) {
{
name: "mtls test",
expected: tutils.GetDetailsMap()["12345"],
- dir: t.TempDir(),
conf: &config.Config{
AgentAPI: config.AgentAPI{
Port: 2345,
@@ -146,51 +144,39 @@ func TestMtlsForApi(t *testing.T) {
},
clientMTLS: true,
},
- // {
- // name: "mtls test, no client cert",
- // expected: tutils.GetDetailsMap()["12345"],
- // dir: t.TempDir(),
- // conf: &config.Config{
- // AgentAPI: config.AgentAPI{
- // Port: 2345,
- // Key: "../../build/certs/server.key",
- // Cert: "../../build/certs/server.crt",
- // },
- // },
- // clientMTLS: false,
- // },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- t.Logf("%v", tt.dir)
+ // t.Logf("%v", tt.dir)
var url string
if tt.conf.AgentAPI.Key != "" {
url = fmt.Sprintf("https://127.0.0.1:%d/nginx", tt.conf.AgentAPI.Port)
-
- certsDir, err := os.MkdirTemp(tt.dir, "certs")
- if err != nil {
- t.Fail()
- }
- t.Logf("%s", certsDir)
} else {
url = fmt.Sprintf("http://localhost:%d/nginx", tt.conf.AgentAPI.Port)
}
-
- pluginUnderTest := NewAgentAPI(tt.conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
- pluginUnderTest.Init(core.NewMockMessagePipe(context.TODO()))
-
client := resty.New()
client.SetDebug(true)
if tt.clientMTLS {
+ cmd := exec.Command("../../scripts/mtls/make_certs.sh")
+
+ err := cmd.Run()
+ if err != nil {
+ t.Logf("%v", err)
+ t.Fail()
+ }
+
transport := &http.Transport{TLSClientConfig: getConfig(t)}
client.SetTransport(transport)
}
+ pluginUnderTest := NewAgentAPI(tt.conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
+ pluginUnderTest.Init(core.NewMockMessagePipe(context.TODO()))
+
resp, err := client.R().EnableTrace().Get(url)
assert.NoError(t, err)
@@ -219,20 +205,6 @@ func TestMtlsForApi(t *testing.T) {
}
func getConfig(t *testing.T) *tls.Config {
- err := os.MkdirAll("../../build/certs", 0755)
- if err != nil {
- t.Logf("%v", err)
- t.Fail()
- }
-
- cmd := exec.Command("../../scripts/mtls/make_certs.sh")
-
- err = cmd.Run()
- if err != nil {
- t.Logf("%v", err)
- t.Fail()
- }
-
crt, err := ioutil.ReadFile("../../build/certs/client.crt")
assert.NoError(t, err)
key, err := ioutil.ReadFile("../../build/certs/client.key")
@@ -240,8 +212,6 @@ func getConfig(t *testing.T) *tls.Config {
ca, err := ioutil.ReadFile("../../build/certs/ca.pem")
assert.NoError(t, err)
- os.Setenv("SSL_CERT_FILE", "../../build/certs/ca.pem")
-
cert, err := tls.X509KeyPair(crt, key)
if err != nil {
assert.Fail(t, "error reading cert")
From 3257ed7655cac6c12f66fac0de65521895fa98c1 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 13:43:45 +0000
Subject: [PATCH 11/21] added test for mlts
---
scripts/mtls/make_certs.sh | 2 +-
src/plugins/agent_api_test.go | 13 +++++++------
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index a932cc921..1b0891baf 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
-scripts_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" 2>&1 && pwd )
+scripts_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
echo "$scripts_dir"
build_dir="$scripts_dir/../../build/certs"
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index c5a25d2ed..e1b53b754 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -9,6 +9,8 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
+ "time"
+
"os"
"os/exec"
@@ -112,7 +114,7 @@ func TestProcess_metricReport(t *testing.T) {
assert.Equal(t, metricReport, agentAPI.exporter.GetLatestMetricReport())
}
-func TestMtlsForApi(t *testing.T) {
+func TestMtls_forApi(t *testing.T) {
tests := []struct {
name string
expected *proto.NginxDetails
@@ -147,8 +149,6 @@ func TestMtlsForApi(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- // t.Logf("%v", tt.dir)
-
var url string
if tt.conf.AgentAPI.Key != "" {
@@ -159,9 +159,8 @@ func TestMtlsForApi(t *testing.T) {
}
client := resty.New()
- client.SetDebug(true)
-
if tt.clientMTLS {
+
cmd := exec.Command("../../scripts/mtls/make_certs.sh")
err := cmd.Run()
@@ -175,7 +174,9 @@ func TestMtlsForApi(t *testing.T) {
}
pluginUnderTest := NewAgentAPI(tt.conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
- pluginUnderTest.Init(core.NewMockMessagePipe(context.TODO()))
+ pluginUnderTest.Init(core.NewMockMessagePipe(context.Background()))
+
+ client.SetRetryCount(3).SetRetryWaitTime(50 * time.Millisecond).SetRetryMaxWaitTime(200 * time.Millisecond)
resp, err := client.R().EnableTrace().Get(url)
From 07fedac03f2ec30d3abc845cfe7157b92fdd3ef4 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 13:49:09 +0000
Subject: [PATCH 12/21] timinig on file existing
---
src/plugins/agent_api_test.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index e1b53b754..ab877c84b 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -169,6 +169,9 @@ func TestMtls_forApi(t *testing.T) {
t.Fail()
}
+ time.Sleep(200 * time.Millisecond)
+
+ assert.FileExists(t, "../../build/certs/server.crt")
transport := &http.Transport{TLSClientConfig: getConfig(t)}
client.SetTransport(transport)
}
From d099a92671531e5cee46c3c00adb0e22a2801472 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 13:54:16 +0000
Subject: [PATCH 13/21] waituntil for file
---
src/plugins/agent_api_test.go | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index ab877c84b..63958f026 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -17,6 +17,7 @@ import (
"testing"
"github.com/go-resty/resty/v2"
+ "github.com/nginx/agent/sdk/v2"
"github.com/nginx/agent/sdk/v2/proto"
"github.com/nginx/agent/v2/src/core"
"github.com/nginx/agent/v2/src/core/config"
@@ -150,6 +151,7 @@ func TestMtls_forApi(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var url string
+ ctx := context.Background()
if tt.conf.AgentAPI.Key != "" {
url = fmt.Sprintf("https://127.0.0.1:%d/nginx", tt.conf.AgentAPI.Port)
@@ -169,15 +171,18 @@ func TestMtls_forApi(t *testing.T) {
t.Fail()
}
- time.Sleep(200 * time.Millisecond)
+ err = sdk.WaitUntil(ctx, 100*time.Millisecond, 100*time.Millisecond, 1*time.Second, func() error {
+ _, err := ioutil.ReadFile("../../build/certs/server.crt")
+ return err
+ })
- assert.FileExists(t, "../../build/certs/server.crt")
+ assert.NoError(t, err)
transport := &http.Transport{TLSClientConfig: getConfig(t)}
client.SetTransport(transport)
}
pluginUnderTest := NewAgentAPI(tt.conf, tutils.GetMockEnvWithProcess(), tutils.GetMockNginxBinary())
- pluginUnderTest.Init(core.NewMockMessagePipe(context.Background()))
+ pluginUnderTest.Init(core.NewMockMessagePipe(ctx))
client.SetRetryCount(3).SetRetryWaitTime(50 * time.Millisecond).SetRetryMaxWaitTime(200 * time.Millisecond)
From f8a2faad1467da3a0ebbdde00e7ad000e73014ba Mon Sep 17 00:00:00 2001
From: dhurley
Date: Mon, 28 Nov 2022 14:17:20 +0000
Subject: [PATCH 14/21] Update mTLS test to verify if make_certs.sh script
succeeded
---
src/plugins/agent_api_test.go | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index 63958f026..adaf13d02 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -162,13 +162,11 @@ func TestMtls_forApi(t *testing.T) {
client := resty.New()
if tt.clientMTLS {
-
- cmd := exec.Command("../../scripts/mtls/make_certs.sh")
-
- err := cmd.Run()
+ output, err := exec.Command("../../scripts/mtls/make_certs.sh").CombinedOutput()
if err != nil {
- t.Logf("%v", err)
- t.Fail()
+ t.Errorf("make_certs.sh output: \n%s \n", output)
+ os.RemoveAll("../../build")
+ t.FailNow()
}
err = sdk.WaitUntil(ctx, 100*time.Millisecond, 100*time.Millisecond, 1*time.Second, func() error {
@@ -244,7 +242,7 @@ func getConfig(t *testing.T) *tls.Config {
}
// explore response object for debugging
-func printResult(resp *resty.Response, err error) (*resty.Response) {
+func printResult(resp *resty.Response, err error) *resty.Response {
fmt.Println("Response Info:")
fmt.Println(" Error :", err)
fmt.Println(" Status Code:", resp.StatusCode())
From 877625218a7de5131cf0daf64f06bf1661c09cfe Mon Sep 17 00:00:00 2001
From: dhurley
Date: Mon, 28 Nov 2022 14:45:38 +0000
Subject: [PATCH 15/21] Update make_certs.sh
---
scripts/mtls/make_certs.sh | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index 1b0891baf..23c2eca62 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -1,4 +1,5 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
set -e
scripts_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
From aacd548bb635d9c6f001a7f2407cbf16146aeca6 Mon Sep 17 00:00:00 2001
From: dhurley
Date: Mon, 28 Nov 2022 14:49:03 +0000
Subject: [PATCH 16/21] Update mTLS test to only clean up certs directory in
build folder
---
src/plugins/agent_api_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/plugins/agent_api_test.go b/src/plugins/agent_api_test.go
index adaf13d02..00d6faabe 100644
--- a/src/plugins/agent_api_test.go
+++ b/src/plugins/agent_api_test.go
@@ -165,7 +165,7 @@ func TestMtls_forApi(t *testing.T) {
output, err := exec.Command("../../scripts/mtls/make_certs.sh").CombinedOutput()
if err != nil {
t.Errorf("make_certs.sh output: \n%s \n", output)
- os.RemoveAll("../../build")
+ os.RemoveAll("../../build/certs/")
t.FailNow()
}
@@ -205,7 +205,7 @@ func TestMtls_forApi(t *testing.T) {
pluginUnderTest.Close()
if tt.clientMTLS {
- os.RemoveAll("../../build")
+ os.RemoveAll("../../build/certs/")
}
})
}
From c17dca5f107c706aeb98fe846ede6ceec72ba26f Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 15:03:57 +0000
Subject: [PATCH 17/21] code review comments
---
src/plugins/agent_api.go | 7 +------
.../github.com/nginx/agent/v2/src/plugins/agent_api.go | 7 +------
2 files changed, 2 insertions(+), 12 deletions(-)
diff --git a/src/plugins/agent_api.go b/src/plugins/agent_api.go
index 1b77fa8d3..1860c5d8f 100644
--- a/src/plugins/agent_api.go
+++ b/src/plugins/agent_api.go
@@ -87,12 +87,7 @@ func (a *AgentAPI) createHttpServer() {
mux.Handle("/metrics", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}))
mux.Handle("/nginx/", a.nginxHandler)
-
- // func(w http.ResponseWriter, req *http.Request) {
- // w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
- // w.Write([]byte("This is an example server.\n"))
- //})
-
+
a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
index 1b77fa8d3..1860c5d8f 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go
@@ -87,12 +87,7 @@ func (a *AgentAPI) createHttpServer() {
mux.Handle("/metrics", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}))
mux.Handle("/nginx/", a.nginxHandler)
-
- // func(w http.ResponseWriter, req *http.Request) {
- // w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
- // w.Write([]byte("This is an example server.\n"))
- //})
-
+
a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
From c7080256e549f3ad36e8b6702b6d0ea9eb80e3b1 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 15:16:57 +0000
Subject: [PATCH 18/21] added default
---
main.go | 7 ++++++-
nginx-agent.conf | 3 ---
sdk/agent/config/config_helpers.go | 2 ++
src/core/config/defaults.go | 3 +--
.../nginx/agent/sdk/v2/agent/config/config_helpers.go | 2 ++
.../github.com/nginx/agent/v2/src/core/config/defaults.go | 3 +--
.../nginx/agent/sdk/v2/agent/config/config_helpers.go | 2 ++
7 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/main.go b/main.go
index 7f2ee419e..4c6eca73e 100644
--- a/main.go
+++ b/main.go
@@ -211,9 +211,14 @@ func loadPlugins(commander client.Commander, binary *core.NginxBinaryType, env *
plugins.NewProcessWatcher(env, binary),
plugins.NewExtensions(loadedConfig, env),
plugins.NewEvents(loadedConfig, env, sdkGRPC.NewMessageMeta(uuid.NewString()), binary),
- plugins.NewAgentAPI(loadedConfig, env, binary),
)
+ if loadedConfig.AgentAPI.Port != 0 {
+ corePlugins = append(corePlugins, plugins.NewAgentAPI(loadedConfig, env, binary))
+ } else {
+ log.Info("Agent API not configured")
+ }
+
if len(loadedConfig.Nginx.NginxCountingSocket) > 0 {
corePlugins = append(corePlugins, plugins.NewNginxCounter(loadedConfig, binary, env))
}
diff --git a/nginx-agent.conf b/nginx-agent.conf
index ebd24b4e9..549de3b38 100644
--- a/nginx-agent.conf
+++ b/nginx-agent.conf
@@ -15,9 +15,6 @@ server:
# provide servername overrides if using SNI
# metrics: ""
# command: ""
-api:
- # port to expose http api
- port: 9090
# tls options
tls:
# enable tls in the nginx-agent setup for grpcs
diff --git a/sdk/agent/config/config_helpers.go b/sdk/agent/config/config_helpers.go
index 62e6492b5..98644a599 100644
--- a/sdk/agent/config/config_helpers.go
+++ b/sdk/agent/config/config_helpers.go
@@ -16,6 +16,7 @@ const (
FeatureProcessWatcher = FeaturesKey + KeyDelimiter + "process-watcher"
FeatureFileWatcher = FeaturesKey + KeyDelimiter + "file-watcher"
FeatureActivityEvents = FeaturesKey + KeyDelimiter + "activity-events"
+ FeatureAgentAPI = FeaturesKey + KeyDelimiter + "agent-api"
)
func GetDefaultFeatures() []string {
@@ -31,5 +32,6 @@ func GetDefaultFeatures() []string {
FeatureProcessWatcher,
FeatureFileWatcher,
FeatureActivityEvents,
+ FeatureAgentAPI,
}
}
diff --git a/src/core/config/defaults.go b/src/core/config/defaults.go
index f0750f2f5..b4a785bf5 100644
--- a/src/core/config/defaults.go
+++ b/src/core/config/defaults.go
@@ -44,7 +44,7 @@ var (
Token: uuid.New().String(),
},
AgentAPI: AgentAPI{
- Port: 9090,
+ Port: 0,
},
Nginx: Nginx{
Debug: false,
@@ -232,7 +232,6 @@ var (
&IntFlag{
Name: AgentAPIPort,
Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.",
- DefaultValue: Defaults.AgentAPI.Port,
},
&StringFlag{
Name: AgentAPICert,
diff --git a/test/performance/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go b/test/performance/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go
index 62e6492b5..98644a599 100644
--- a/test/performance/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go
+++ b/test/performance/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go
@@ -16,6 +16,7 @@ const (
FeatureProcessWatcher = FeaturesKey + KeyDelimiter + "process-watcher"
FeatureFileWatcher = FeaturesKey + KeyDelimiter + "file-watcher"
FeatureActivityEvents = FeaturesKey + KeyDelimiter + "activity-events"
+ FeatureAgentAPI = FeaturesKey + KeyDelimiter + "agent-api"
)
func GetDefaultFeatures() []string {
@@ -31,5 +32,6 @@ func GetDefaultFeatures() []string {
FeatureProcessWatcher,
FeatureFileWatcher,
FeatureActivityEvents,
+ FeatureAgentAPI,
}
}
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
index f0750f2f5..b4a785bf5 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
@@ -44,7 +44,7 @@ var (
Token: uuid.New().String(),
},
AgentAPI: AgentAPI{
- Port: 9090,
+ Port: 0,
},
Nginx: Nginx{
Debug: false,
@@ -232,7 +232,6 @@ var (
&IntFlag{
Name: AgentAPIPort,
Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.",
- DefaultValue: Defaults.AgentAPI.Port,
},
&StringFlag{
Name: AgentAPICert,
diff --git a/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go b/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go
index 62e6492b5..98644a599 100644
--- a/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go
+++ b/vendor/github.com/nginx/agent/sdk/v2/agent/config/config_helpers.go
@@ -16,6 +16,7 @@ const (
FeatureProcessWatcher = FeaturesKey + KeyDelimiter + "process-watcher"
FeatureFileWatcher = FeaturesKey + KeyDelimiter + "file-watcher"
FeatureActivityEvents = FeaturesKey + KeyDelimiter + "activity-events"
+ FeatureAgentAPI = FeaturesKey + KeyDelimiter + "agent-api"
)
func GetDefaultFeatures() []string {
@@ -31,5 +32,6 @@ func GetDefaultFeatures() []string {
FeatureProcessWatcher,
FeatureFileWatcher,
FeatureActivityEvents,
+ FeatureAgentAPI,
}
}
From 5c54e9dee034e9fd35a3cae0b734becd140d71ae Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 17:26:08 +0000
Subject: [PATCH 19/21] tidy script
---
scripts/mtls/make_certs.sh | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index 23c2eca62..e7d91d3c9 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -18,7 +18,7 @@ if [ ! -d "$build_dir" ]; then
mkdir -p "$build_dir";
fi
-make_ca() {
+ca() {
echo "Creating Self-Signed Root CA certificate and key"
openssl req \
-new -newkey rsa:4096 \
@@ -32,7 +32,7 @@ make_ca() {
-days 1
}
-make_int() {
+intermediate() {
echo "Creating Intermediate CA certificate and key"
openssl req \
-new -newkey rsa:4096 \
@@ -58,7 +58,7 @@ make_int() {
cat "$build_dir"/ca_int.crt "$build_dir"/ca.crt > "$build_dir"/ca.pem
}
-make_server() {
+server() {
echo "Creating nginx-manger certificate and key"
openssl req \
-new -newkey rsa:4096 \
@@ -81,7 +81,7 @@ make_server() {
openssl verify -CAfile "$build_dir"/ca.pem "$build_dir"/server.crt
}
-make_client() {
+client() {
echo "Creating Client certificate and key"
openssl req \
-new -newkey rsa:4096 \
@@ -95,7 +95,7 @@ make_client() {
-sha256 \
-CA "$build_dir"/ca.crt \
-CAkey "$build_dir"/ca.key \
- -CAcreateserial \
+ -CAserial "$scripts_dir"/.srl \
-in "$build_dir"/client.csr \
-out "$build_dir"/client.crt \
-extfile "$scripts_dir"/client.cnf \
@@ -106,7 +106,7 @@ make_client() {
# MAIN
cd "$scripts_dir"
-make_ca
-make_int
-make_server
-make_client
+ca
+intermediate
+server
+client
From 958d07148343d4f22e6e9c4d196b282a16c5180f Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Mon, 28 Nov 2022 17:57:50 +0000
Subject: [PATCH 20/21] changed default port to 8081
---
Makefile | 3 ---
nginx-agent.conf | 4 ++++
src/core/config/defaults.go | 3 ---
.../github.com/nginx/agent/v2/src/core/config/defaults.go | 3 ---
4 files changed, 4 insertions(+), 9 deletions(-)
diff --git a/Makefile b/Makefile
index aebfeb0eb..35a971aa8 100644
--- a/Makefile
+++ b/Makefile
@@ -198,9 +198,6 @@ certs: ## Generate TLS certificates
cp ${CERTS_DIR}/server/ee.crt ${CERTS_DIR}/server.crt
cp ${CERTS_DIR}/server/ee.key ${CERTS_DIR}/server.key
-certs-new: ## Generate TLS certificates
- scripts/mtls/make_certs.sh
-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Docker Helper Targets #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
diff --git a/nginx-agent.conf b/nginx-agent.conf
index 549de3b38..049257ec4 100644
--- a/nginx-agent.conf
+++ b/nginx-agent.conf
@@ -59,3 +59,7 @@ metrics:
# OSS NGINX default config path
# path to aux file dirs can also be added
config_dirs: "/etc/nginx:/usr/local/etc/nginx:/usr/share/nginx/modules:/etc/nms"
+
+api:
+ # default port for Agent API, this is for the server configuration of the REST API
+ port: 8081
diff --git a/src/core/config/defaults.go b/src/core/config/defaults.go
index b4a785bf5..979a086a7 100644
--- a/src/core/config/defaults.go
+++ b/src/core/config/defaults.go
@@ -43,9 +43,6 @@ var (
// so setting to random uuid at the moment, tls connection won't work without the auth header
Token: uuid.New().String(),
},
- AgentAPI: AgentAPI{
- Port: 0,
- },
Nginx: Nginx{
Debug: false,
NginxCountingSocket: "unix:/var/run/nginx-agent/nginx.sock",
diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
index b4a785bf5..979a086a7 100644
--- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
+++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go
@@ -43,9 +43,6 @@ var (
// so setting to random uuid at the moment, tls connection won't work without the auth header
Token: uuid.New().String(),
},
- AgentAPI: AgentAPI{
- Port: 0,
- },
Nginx: Nginx{
Debug: false,
NginxCountingSocket: "unix:/var/run/nginx-agent/nginx.sock",
From cd52672606f1eda2663d43af22ffcab0c78a8c49 Mon Sep 17 00:00:00 2001
From: "o.omahony"
Date: Tue, 29 Nov 2022 16:20:56 +0000
Subject: [PATCH 21/21] tidied up the example
---
examples/grafana-metrics/Dockerfile | 2 +-
examples/grafana-metrics/README.md | 9 +
examples/grafana-metrics/docker-compose.yml | 3 +-
.../nginx-agent-dashboard.json | 1171 +++++++++--------
examples/grafana-metrics/nginx-agent.conf | 2 +-
examples/grafana-metrics/nginx.conf | 7 +-
examples/grafana-metrics/prometheus.yml | 2 +-
scripts/mtls/make_certs.sh | 10 +-
8 files changed, 609 insertions(+), 597 deletions(-)
diff --git a/examples/grafana-metrics/Dockerfile b/examples/grafana-metrics/Dockerfile
index 2f0208f8b..eaf8c2f1b 100644
--- a/examples/grafana-metrics/Dockerfile
+++ b/examples/grafana-metrics/Dockerfile
@@ -39,6 +39,6 @@ COPY --from=install /agent/nginx-agent.conf /etc/nginx-agent/nginx-agent.conf
RUN chmod +x /agent/entrypoint.sh
STOPSIGNAL SIGTERM
-EXPOSE 80 443
+EXPOSE 8080
ENTRYPOINT ["/agent/entrypoint.sh"]
diff --git a/examples/grafana-metrics/README.md b/examples/grafana-metrics/README.md
index c0f5bbe41..b7612a380 100644
--- a/examples/grafana-metrics/README.md
+++ b/examples/grafana-metrics/README.md
@@ -7,6 +7,15 @@ This example demonstrates how the NGINX agent can be used to report metrics usin
make clean build run
```
+## List of Services
+The services run by this example are listed below
+| Service | Port |
+|-------------|-------|
+| Grafana | 3000 |
+| NGINX | 8080 |
+| Grafana | 8081 |
+| Prometheus | 9090 |
+
## Example of Grafana Dashboard

diff --git a/examples/grafana-metrics/docker-compose.yml b/examples/grafana-metrics/docker-compose.yml
index 64ee17fc9..3f4427544 100644
--- a/examples/grafana-metrics/docker-compose.yml
+++ b/examples/grafana-metrics/docker-compose.yml
@@ -13,7 +13,8 @@ services:
image: nginx/agent-example
container_name: agent
ports:
- - 9091:9091
+ - 8080:8080
+ - 8081:8081
networks:
- monitoring
prometheus:
diff --git a/examples/grafana-metrics/nginx-agent-dashboard.json b/examples/grafana-metrics/nginx-agent-dashboard.json
index 10af02342..ce50ad8f7 100644
--- a/examples/grafana-metrics/nginx-agent-dashboard.json
+++ b/examples/grafana-metrics/nginx-agent-dashboard.json
@@ -1,25 +1,25 @@
{
"annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "target": {
- "limit": 100,
- "matchAny": false,
- "tags": [],
- "type": "dashboard"
- },
- "type": "dashboard"
- }
- ]
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
},
"editable": true,
"fiscalYearStartMonth": 0,
@@ -28,633 +28,634 @@
"links": [],
"liveNow": false,
"panels": [
- {
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "percent"
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
},
- "overrides": []
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 5,
+ "x": 0,
+ "y": 0
+ },
+ "id": 19,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "mean"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.2.1",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "gridPos": {
- "h": 7,
- "w": 5,
- "x": 0,
- "y": 0
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "system_cpu_system + system_cpu_user",
+ "instant": true,
+ "legendFormat": "CPU %",
+ "range": false,
+ "refId": "A"
+ }
+ ],
+ "title": "CPU %",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
},
- "id": 19,
- "options": {
- "orientation": "auto",
- "reduceOptions": {
- "calcs": [
- "mean"
- ],
- "fields": "",
- "values": false
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
},
- "showThresholdLabels": false,
- "showThresholdMarkers": true
- },
- "pluginVersion": "9.2.3",
- "targets": [
{
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "code",
- "exemplar": false,
- "expr": "system_cpu_system + system_cpu_user",
- "instant": true,
- "legendFormat": "CPU %",
- "range": false,
- "refId": "A"
+ "color": "red",
+ "value": 80
}
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 5,
+ "x": 5,
+ "y": 0
+ },
+ "id": 18,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "mean"
],
- "title": "CPU %",
- "type": "gauge"
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
},
- {
+ "pluginVersion": "9.2.1",
+ "targets": [
+ {
"datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "percent"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 7,
- "w": 5,
- "x": 5,
- "y": 0
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "system_mem_used / system_mem_total * 100",
+ "instant": true,
+ "legendFormat": "% memory used",
+ "range": false,
+ "refId": "A"
+ }
+ ],
+ "title": "Memory %",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
},
- "id": 18,
- "options": {
- "orientation": "auto",
- "reduceOptions": {
- "calcs": [
- "mean"
- ],
- "fields": "",
- "values": false
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
},
- "showThresholdLabels": false,
- "showThresholdMarkers": true
- },
- "pluginVersion": "9.2.3",
- "targets": [
{
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "code",
- "exemplar": false,
- "expr": "system_mem_used / system_mem_total * 100",
- "instant": true,
- "legendFormat": "% memory used",
- "range": false,
- "refId": "A"
+ "color": "red",
+ "value": 80
}
+ ]
+ },
+ "unit": "percent"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 5,
+ "x": 10,
+ "y": 0
+ },
+ "id": 20,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "mean"
],
- "title": "Memory %",
- "type": "gauge"
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
},
- {
+ "pluginVersion": "9.2.1",
+ "targets": [
+ {
"datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "percent"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 7,
- "w": 5,
- "x": 10,
- "y": 0
+ "editorMode": "builder",
+ "exemplar": false,
+ "expr": "system_swap_pct_free",
+ "instant": true,
+ "legendFormat": "Swap used",
+ "range": false,
+ "refId": "A"
+ }
+ ],
+ "title": "Swap Free %",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
},
- "id": 20,
- "options": {
- "orientation": "auto",
- "reduceOptions": {
- "calcs": [
- "mean"
- ],
- "fields": "",
- "values": false
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
},
- "showThresholdLabels": false,
- "showThresholdMarkers": true
- },
- "pluginVersion": "9.2.3",
- "targets": [
{
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "builder",
- "exemplar": false,
- "expr": "system_swap_pct_free",
- "instant": true,
- "legendFormat": "Swap used",
- "range": false,
- "refId": "A"
+ "color": "red",
+ "value": 80
}
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 9,
+ "x": 15,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
],
- "title": "Swap Free %",
- "type": "gauge"
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
},
- {
+ "pluginVersion": "9.2.1",
+ "targets": [
+ {
"datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
+ "editorMode": "builder",
+ "expr": "nginx_status",
+ "legendFormat": "Status",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "gridPos": {
- "h": 7,
- "w": 9,
- "x": 15,
- "y": 0
+ "editorMode": "builder",
+ "expr": "nginx_workers_count",
+ "hide": false,
+ "legendFormat": "Worker Count",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "NGINX",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
},
- "id": 2,
- "options": {
- "orientation": "auto",
- "reduceOptions": {
- "calcs": [
- "lastNotNull"
- ],
- "fields": "",
- "values": false
- },
- "showThresholdLabels": false,
- "showThresholdMarkers": true
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
},
- "pluginVersion": "9.2.3",
- "targets": [
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
{
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "builder",
- "expr": "nginx_status",
- "legendFormat": "Status",
- "range": true,
- "refId": "A"
+ "color": "green",
+ "value": null
},
{
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "builder",
- "expr": "nginx_workers_count",
- "hide": false,
- "legendFormat": "Worker Count",
- "range": true,
- "refId": "B"
+ "color": "red",
+ "value": 80
}
- ],
- "title": "NGINX",
- "type": "gauge"
+ ]
+ }
+ },
+ "overrides": []
},
- {
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 7
+ },
+ "id": 22,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.2.3",
+ "targets": [
+ {
"datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 0,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "auto",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "rate(nginx_http_request_count[1m]) * 60",
+ "instant": false,
+ "legendFormat": "requests / min",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "HTTP requests per minute",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
},
- "gridPos": {
- "h": 9,
- "w": 8,
- "x": 0,
- "y": 7
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
},
- "id": 22,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
},
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "pluginVersion": "9.2.3",
- "targets": [
{
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "code",
- "exemplar": false,
- "expr": "rate(nginx_http_request_count[1m]) * 60",
- "instant": false,
- "legendFormat": "requests / min",
- "range": true,
- "refId": "A"
+ "color": "red",
+ "value": 80
}
- ],
- "title": "HTTP requests per minute",
- "type": "timeseries"
+ ]
+ }
+ },
+ "overrides": []
},
- {
+ "gridPos": {
+ "h": 9,
+ "w": 7,
+ "x": 8,
+ "y": 7
+ },
+ "id": 24,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.2.3",
+ "targets": [
+ {
"datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 0,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "auto",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "rate(nginx_http_request_bytes_sent[1m]) * 60",
+ "instant": false,
+ "legendFormat": "bytes / min",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "HTTP bytes sent per minute",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
},
- "gridPos": {
- "h": 9,
- "w": 7,
- "x": 8,
- "y": 7
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineStyle": {
+ "fill": "solid"
+ },
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": true,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
},
- "id": 24,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
},
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "pluginVersion": "9.2.3",
- "targets": [
{
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "code",
- "exemplar": false,
- "expr": "rate(nginx_http_request_bytes_sent[1m]) * 60",
- "instant": false,
- "legendFormat": "bytes / min",
- "range": true,
- "refId": "A"
+ "color": "red",
+ "value": 80
}
- ],
- "title": "HTTP bytes sent per minute",
- "type": "timeseries"
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
},
- {
+ "gridPos": {
+ "h": 9,
+ "w": 9,
+ "x": 15,
+ "y": 7
+ },
+ "id": 14,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.2.3",
+ "targets": [
+ {
"datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineInterpolation": "linear",
- "lineStyle": {
- "fill": "solid"
- },
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "auto",
- "spanNulls": true,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
+ "editorMode": "builder",
+ "expr": "nginx_http_status_1xx",
+ "hide": false,
+ "legendFormat": "{{__name__}}",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "gridPos": {
- "h": 9,
- "w": 9,
- "x": 15,
- "y": 7
+ "editorMode": "code",
+ "expr": "nginx_http_status_2xx",
+ "legendFormat": "{{__name__}}",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "id": 14,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "none"
- }
+ "editorMode": "builder",
+ "expr": "nginx_http_status_3xx",
+ "hide": false,
+ "legendFormat": "{{__name__}}",
+ "range": true,
+ "refId": "C"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
},
- "pluginVersion": "9.2.3",
- "targets": [
- {
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "builder",
- "expr": "nginx_http_status_1xx",
- "hide": false,
- "legendFormat": "{{__name__}}",
- "range": true,
- "refId": "B"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "code",
- "expr": "nginx_http_status_2xx",
- "legendFormat": "{{__name__}}",
- "range": true,
- "refId": "A"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "builder",
- "expr": "nginx_http_status_3xx",
- "hide": false,
- "legendFormat": "{{__name__}}",
- "range": true,
- "refId": "C"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "builder",
- "expr": "nginx_http_status_4xx",
- "hide": false,
- "legendFormat": "{{__name__}}",
- "range": true,
- "refId": "D"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "editorMode": "builder",
- "expr": "nginx_http_status_5xx",
- "hide": false,
- "legendFormat": "{{__name__}}",
- "range": true,
- "refId": "E"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "PBFA97CFB590B2093"
- },
- "hide": false,
- "refId": "F"
- }
- ],
- "title": "NGINX HTTP Responses",
- "transformations": [],
- "type": "timeseries"
- }
+ "editorMode": "builder",
+ "expr": "nginx_http_status_4xx",
+ "hide": false,
+ "legendFormat": "{{__name__}}",
+ "range": true,
+ "refId": "D"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editorMode": "builder",
+ "expr": "nginx_http_status_5xx",
+ "hide": false,
+ "legendFormat": "{{__name__}}",
+ "range": true,
+ "refId": "E"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "hide": false,
+ "refId": "F"
+ }
+ ],
+ "title": "NGINX HTTP Responses",
+ "transformations": [],
+ "type": "timeseries"
+ }
],
+ "refresh": "5s",
"schemaVersion": 37,
"style": "dark",
"tags": [],
"templating": {
- "list": []
+ "list": []
},
"time": {
- "from": "now-1h",
- "to": "now"
+ "from": "now-5m",
+ "to": "now"
},
"timepicker": {},
"timezone": "",
@@ -662,4 +663,4 @@
"uid": "zjoqqbd4k",
"version": 2,
"weekStart": ""
-}
+ }
diff --git a/examples/grafana-metrics/nginx-agent.conf b/examples/grafana-metrics/nginx-agent.conf
index b0212e62f..48464cd82 100644
--- a/examples/grafana-metrics/nginx-agent.conf
+++ b/examples/grafana-metrics/nginx-agent.conf
@@ -9,7 +9,7 @@
api:
# port to expose http api
- port: 9091
+ port: 8081
# tls options
tls:
# enable tls in the nginx-agent setup for grpcs
diff --git a/examples/grafana-metrics/nginx.conf b/examples/grafana-metrics/nginx.conf
index c57a0f451..ffce79ff4 100644
--- a/examples/grafana-metrics/nginx.conf
+++ b/examples/grafana-metrics/nginx.conf
@@ -35,7 +35,7 @@ http {
#gzip on;
server {
- listen 80;
+ listen 8080;
server_name localhost;
#charset koi8-r;
@@ -45,6 +45,11 @@ http {
location / {
root /usr/local/nginx/html;
index index.html index.htm;
+ allow 127.0.0.1;
+ }
+
+ location /test {
+ proxy_pass http://www.example.com;
}
##
diff --git a/examples/grafana-metrics/prometheus.yml b/examples/grafana-metrics/prometheus.yml
index 306ebd75b..45990a687 100755
--- a/examples/grafana-metrics/prometheus.yml
+++ b/examples/grafana-metrics/prometheus.yml
@@ -5,4 +5,4 @@ scrape_configs:
- job_name: "prometheus"
scrape_interval: 15s
static_configs:
- - targets: ["agent:9091"]
+ - targets: ["agent:8081"]
diff --git a/scripts/mtls/make_certs.sh b/scripts/mtls/make_certs.sh
index e7d91d3c9..378ecba56 100755
--- a/scripts/mtls/make_certs.sh
+++ b/scripts/mtls/make_certs.sh
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
-
+# See https://stackoverflow.com/questions/34612991/openssl-key-generation-on-os-x-failing
+# On Mac OS X use openssl to run this script
set -e
scripts_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
@@ -8,11 +9,6 @@ echo "$scripts_dir"
build_dir="$scripts_dir/../../build/certs"
echo "$build_dir"
-if [ -f .srl ]; then
- rm .srl
- echo ".srl removed"
-fi
-
if [ ! -d "$build_dir" ]; then
echo "creating certs directory"
mkdir -p "$build_dir";
@@ -95,7 +91,7 @@ client() {
-sha256 \
-CA "$build_dir"/ca.crt \
-CAkey "$build_dir"/ca.key \
- -CAserial "$scripts_dir"/.srl \
+ -CAserial "$build_dir"/ca_int.srl \
-in "$build_dir"/client.csr \
-out "$build_dir"/client.crt \
-extfile "$scripts_dir"/client.cnf \