diff --git a/README.md b/README.md index cb8dd7ffd..2a4f3ac9c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ | Branch | Status | -|:------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| :----: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | master | [![Build status](https://github.com/jfrog/jfrog-client-go/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/jfrog/jfrog-client-go/actions) [![Static Analysis](https://github.com/jfrog/jfrog-client-go/actions/workflows/analysis.yml/badge.svg?branch=master)](https://github.com/jfrog/jfrog-client-go/actions/workflows/analysis.yml) | | dev | [![Build status](https://github.com/jfrog/jfrog-client-go/actions/workflows/tests.yml/badge.svg?branch=dev)](https://github.com/jfrog/jfrog-client-go/actions) [![Static Analysis](https://github.com/jfrog/jfrog-client-go/actions/workflows/analysis.yml/badge.svg?branch=dev)](https://github.com/jfrog/jfrog-client-go/actions/workflows/analysis.yml) | @@ -251,7 +251,7 @@ content of this repository is deleted. #### Test Types | Type | Description | Prerequisites | -|----------------------|--------------------|-------------------------------| +| -------------------- | ------------------ | ----------------------------- | | `-test.artifactory` | Artifactory tests | Artifactory Pro | | `-test.distribution` | Distribution tests | Artifactory with Distribution | | `-test.xray` | Xray tests | Artifactory with Xray | @@ -262,7 +262,7 @@ content of this repository is deleted. #### Connection Details | Flag | Description | -|---------------------|--------------------------------------------------------------------------------------------------------| +| ------------------- | ------------------------------------------------------------------------------------------------------ | | `-rt.url` | [Default: http://localhost:8081/artifactory] Artifactory URL. | | `-ds.url` | [Optional] JFrog Distribution URL. | | `-xr.url` | [Optional] JFrog Xray URL. | @@ -357,8 +357,10 @@ serviceConfig, err := config.NewConfigBuilder(). SetDryRun(false). // Add [Context](https://golang.org/pkg/context/) SetContext(ctx). - // Optionally overwrite the default HTTP timeout, which is set to 30 seconds. - SetHttpTimeout(180 * time.Second). + // Optionally overwrite the default dial timeout, which is set to 30 seconds. + SetDialTimeout(180 * time.Second). + // Optionally set the total HTTP request timeout. + SetOverallRequestTimeout(10 * time.Minute). // Optionally overwrite the default HTTP retries, which is set to 3. SetHttpRetries(8). Build() @@ -1965,7 +1967,7 @@ vulnerabilitiesReportRequest := services.VulnerabilitiesReportRequestParams{ Severity: []string{ "High", "Medium" - }, + }, CvssScore: services.CvssScore { MinScore: float64(6.3), MaxScore: float64(9) @@ -2020,6 +2022,7 @@ reportContent, err := xrayManager.ReportContent(reportContentRequest) // The reportId argument value is returned as part of the xrayManager.GenerateVulnerabilitiesReport API response. err := xrayManager.DeleteReport(reportId) ``` + #### Generate Licences Report ```go @@ -2119,7 +2122,7 @@ violationsReportRequest := services.ViolationsReportRequestParams{ Severity: []string{ "High", "Medium" - }, + }, CvssScore: services.CvssScore { MinScore: float64(6.3), MaxScore: float64(9) @@ -2132,7 +2135,7 @@ violationsReportRequest := services.ViolationsReportRequestParams{ Start: "2020-06-29T12:22:16Z", End: "2020-06-29T12:22:16Z" }, - SummaryContains: "kernel", + SummaryContains: "kernel", HasRemediation: &falseValue, }, LicenseFilters: services.LicensesFilter { diff --git a/access/manager.go b/access/manager.go index 84a8b64ef..3435973e7 100644 --- a/access/manager.go +++ b/access/manager.go @@ -23,6 +23,8 @@ func New(config config.Config) (*AccessServicesManager, error) { SetClientCertKeyPath(details.GetClientCertKeyPath()). AppendPreRequestInterceptor(details.RunPreRequestFunctions). SetContext(config.GetContext()). + SetDialTimeout(config.GetDialTimeout()). + SetOverallRequestTimeout(config.GetOverallRequestTimeout()). SetRetries(config.GetHttpRetries()). SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()). Build() diff --git a/artifactory/manager.go b/artifactory/manager.go index a98eb495c..8827abd28 100644 --- a/artifactory/manager.go +++ b/artifactory/manager.go @@ -37,7 +37,8 @@ func NewWithProgress(config config.Config, progress ioutils.ProgressMgr) (Artifa SetCertificatesPath(config.GetCertificatesPath()). SetInsecureTls(config.IsInsecureTls()). SetContext(config.GetContext()). - SetTimeout(config.GetHttpTimeout()). + SetDialTimeout(config.GetDialTimeout()). + SetOverallRequestTimeout(config.GetOverallRequestTimeout()). SetClientCertPath(artDetails.GetClientCertPath()). SetClientCertKeyPath(artDetails.GetClientCertKeyPath()). AppendPreRequestInterceptor(artDetails.RunPreRequestFunctions). diff --git a/auth/servicedetails.go b/auth/servicedetails.go index 36a807fba..63736842a 100644 --- a/auth/servicedetails.go +++ b/auth/servicedetails.go @@ -1,10 +1,11 @@ package auth import ( - "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" "sync" "time" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" @@ -44,7 +45,8 @@ type ServiceDetails interface { SetSshPassphrase(sshPassphrase string) SetSshAuthHeaders(sshAuthHeaders map[string]string) SetClient(client *jfroghttpclient.JfrogHttpClient) - SetHttpTimeout(httpTimeout time.Duration) + SetDialTimeout(dialTimeout time.Duration) + SetOverallRequestTimeout(overallRequestTimeout time.Duration) IsSshAuthHeaderSet() bool IsSshAuthentication() bool @@ -71,7 +73,8 @@ type CommonConfigFields struct { SshAuthHeaders map[string]string `json:"-"` TokenMutex sync.Mutex client *jfroghttpclient.JfrogHttpClient - httpTimeout time.Duration + dialTimeout time.Duration + overallRequestTimeout time.Duration } func (ccf *CommonConfigFields) GetUrl() string { @@ -178,8 +181,12 @@ func (ccf *CommonConfigFields) SetClient(client *jfroghttpclient.JfrogHttpClient ccf.client = client } -func (ccf *CommonConfigFields) SetHttpTimeout(httpTimeout time.Duration) { - ccf.httpTimeout = httpTimeout +func (ccf *CommonConfigFields) SetDialTimeout(dialTimeout time.Duration) { + ccf.dialTimeout = dialTimeout +} + +func (ccf *CommonConfigFields) SetOverallRequestTimeout(overallRequestTimeout time.Duration) { + ccf.overallRequestTimeout = overallRequestTimeout } func (ccf *CommonConfigFields) IsSshAuthHeaderSet() bool { @@ -267,9 +274,12 @@ func SshTokenRefreshPreRequestInterceptor(fields *CommonConfigFields, httpClient func (ccf *CommonConfigFields) CreateHttpClientDetails() httputils.HttpClientDetails { return httputils.HttpClientDetails{ - User: ccf.User, - Password: ccf.Password, - ApiKey: ccf.ApiKey, - AccessToken: ccf.AccessToken, - Headers: utils.CopyMap(ccf.GetSshAuthHeaders())} + User: ccf.User, + Password: ccf.Password, + ApiKey: ccf.ApiKey, + AccessToken: ccf.AccessToken, + Headers: utils.CopyMap(ccf.GetSshAuthHeaders()), + DialTimeout: ccf.dialTimeout, + OverallRequestTimeout: ccf.overallRequestTimeout, + } } diff --git a/config/config.go b/config/config.go index 250766e00..892aaec3c 100644 --- a/config/config.go +++ b/config/config.go @@ -2,10 +2,11 @@ package config import ( "context" - "github.com/jfrog/jfrog-client-go/auth" - "github.com/jfrog/jfrog-client-go/utils/log" "net/http" "time" + + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/utils/log" ) type Config interface { @@ -16,7 +17,8 @@ type Config interface { GetLogger() log.Log IsInsecureTls() bool GetContext() context.Context - GetHttpTimeout() time.Duration + GetDialTimeout() time.Duration + GetOverallRequestTimeout() time.Duration GetHttpRetries() int GetHttpRetryWaitMilliSecs() int GetHttpClient() *http.Client @@ -30,7 +32,8 @@ type servicesConfig struct { logger log.Log insecureTls bool ctx context.Context - httpTimeout time.Duration + dialTimeout time.Duration + overallRequestTimeout time.Duration httpRetries int httpRetryWaitMilliSecs int httpClient *http.Client @@ -64,8 +67,12 @@ func (config *servicesConfig) GetContext() context.Context { return config.ctx } -func (config *servicesConfig) GetHttpTimeout() time.Duration { - return config.httpTimeout +func (config *servicesConfig) GetDialTimeout() time.Duration { + return config.dialTimeout +} + +func (config *servicesConfig) GetOverallRequestTimeout() time.Duration { + return config.overallRequestTimeout } func (config *servicesConfig) GetHttpRetries() int { diff --git a/config/configbuilder.go b/config/configbuilder.go index e1cad4544..a6fe06433 100644 --- a/config/configbuilder.go +++ b/config/configbuilder.go @@ -2,16 +2,18 @@ package config import ( "context" - "github.com/jfrog/jfrog-client-go/auth" - "github.com/jfrog/jfrog-client-go/http/httpclient" "net/http" "time" + + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/http/httpclient" + "github.com/jfrog/jfrog-client-go/utils/log" ) func NewConfigBuilder() *servicesConfigBuilder { configBuilder := &servicesConfigBuilder{} configBuilder.threads = 3 - configBuilder.httpTimeout = httpclient.DefaultHttpTimeout + configBuilder.dialTimeout = httpclient.DefaultDialTimeout configBuilder.httpRetries = 3 configBuilder.httpRetryWaitMilliSecs = 0 return configBuilder @@ -24,7 +26,8 @@ type servicesConfigBuilder struct { isDryRun bool insecureTls bool ctx context.Context - httpTimeout time.Duration + dialTimeout time.Duration + overallRequestTimeout time.Duration httpRetries int httpRetryWaitMilliSecs int httpClient *http.Client @@ -60,8 +63,19 @@ func (builder *servicesConfigBuilder) SetContext(ctx context.Context) *servicesC return builder } -func (builder *servicesConfigBuilder) SetHttpTimeout(httpTimeout time.Duration) *servicesConfigBuilder { - builder.httpTimeout = httpTimeout +// Deprecated +func (builder *servicesConfigBuilder) SetHttpTimeout(dialTimeout time.Duration) *servicesConfigBuilder { + log.Warn("SetHttpTimeout is deprecated. Use SetDialTimeout instead.") + return builder.SetDialTimeout(dialTimeout) +} + +func (builder *servicesConfigBuilder) SetDialTimeout(dialTimeout time.Duration) *servicesConfigBuilder { + builder.dialTimeout = dialTimeout + return builder +} + +func (builder *servicesConfigBuilder) SetOverallRequestTimeout(overallRequestTimeout time.Duration) *servicesConfigBuilder { + builder.overallRequestTimeout = overallRequestTimeout return builder } @@ -88,7 +102,8 @@ func (builder *servicesConfigBuilder) Build() (Config, error) { c.dryRun = builder.isDryRun c.insecureTls = builder.insecureTls c.ctx = builder.ctx - c.httpTimeout = builder.httpTimeout + c.dialTimeout = builder.dialTimeout + c.overallRequestTimeout = builder.overallRequestTimeout c.httpRetries = builder.httpRetries c.httpRetryWaitMilliSecs = builder.httpRetryWaitMilliSecs c.httpClient = builder.httpClient diff --git a/distribution/manager.go b/distribution/manager.go index ad2691cfd..e0548429a 100644 --- a/distribution/manager.go +++ b/distribution/manager.go @@ -21,7 +21,8 @@ func New(config config.Config) (*DistributionServicesManager, error) { SetCertificatesPath(config.GetCertificatesPath()). SetInsecureTls(config.IsInsecureTls()). SetContext(config.GetContext()). - SetTimeout(config.GetHttpTimeout()). + SetDialTimeout(config.GetDialTimeout()). + SetOverallRequestTimeout(config.GetOverallRequestTimeout()). SetClientCertPath(details.GetClientCertPath()). SetClientCertKeyPath(details.GetClientCertKeyPath()). AppendPreRequestInterceptor(details.RunPreRequestFunctions). diff --git a/http/httpclient/clientBuilder.go b/http/httpclient/clientBuilder.go index 3c4dec591..73a579553 100644 --- a/http/httpclient/clientBuilder.go +++ b/http/httpclient/clientBuilder.go @@ -11,24 +11,25 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" ) -var DefaultHttpTimeout = 30 * time.Second +var DefaultDialTimeout = 30 * time.Second func ClientBuilder() *httpClientBuilder { builder := &httpClientBuilder{} - builder.SetTimeout(DefaultHttpTimeout) + builder.SetDialTimeout(DefaultDialTimeout) return builder } type httpClientBuilder struct { - certificatesDirPath string - clientCertPath string - clientCertKeyPath string - insecureTls bool - ctx context.Context - timeout time.Duration - retries int - retryWaitMilliSecs int - httpClient *http.Client + certificatesDirPath string + clientCertPath string + clientCertKeyPath string + insecureTls bool + ctx context.Context + dialTimeout time.Duration + overallRequestTimeout time.Duration + retries int + retryWaitMilliSecs int + httpClient *http.Client } func (builder *httpClientBuilder) SetCertificatesPath(certificatesPath string) *httpClientBuilder { @@ -61,8 +62,13 @@ func (builder *httpClientBuilder) SetContext(ctx context.Context) *httpClientBui return builder } -func (builder *httpClientBuilder) SetTimeout(timeout time.Duration) *httpClientBuilder { - builder.timeout = timeout +func (builder *httpClientBuilder) SetDialTimeout(dialTimeout time.Duration) *httpClientBuilder { + builder.dialTimeout = dialTimeout + return builder +} + +func (builder *httpClientBuilder) SetOverallRequestTimeout(overallRequestTimeout time.Duration) *httpClientBuilder { + builder.overallRequestTimeout = overallRequestTimeout return builder } @@ -107,14 +113,14 @@ func (builder *httpClientBuilder) Build() (*HttpClient, error) { } } err = builder.AddClientCertToTransport(transport) - return &HttpClient{client: &http.Client{Transport: transport}, ctx: builder.ctx, retries: builder.retries, retryWaitMilliSecs: builder.retryWaitMilliSecs}, err + return &HttpClient{client: &http.Client{Transport: transport, Timeout: builder.overallRequestTimeout}, ctx: builder.ctx, retries: builder.retries, retryWaitMilliSecs: builder.retryWaitMilliSecs}, err } func (builder *httpClientBuilder) createDefaultHttpTransport() *http.Transport { return &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ - Timeout: builder.timeout, + Timeout: builder.dialTimeout, KeepAlive: 20 * time.Second, DualStack: true, }).DialContext, diff --git a/http/jfroghttpclient/clientbuilder.go b/http/jfroghttpclient/clientbuilder.go index 3fc18d937..d526c8d8c 100644 --- a/http/jfroghttpclient/clientbuilder.go +++ b/http/jfroghttpclient/clientbuilder.go @@ -2,14 +2,15 @@ package jfroghttpclient import ( "context" - "github.com/jfrog/jfrog-client-go/http/httpclient" "net/http" "time" + + "github.com/jfrog/jfrog-client-go/http/httpclient" ) func JfrogClientBuilder() *jfrogHttpClientBuilder { builder := &jfrogHttpClientBuilder{} - builder.SetTimeout(httpclient.DefaultHttpTimeout) + builder.SetDialTimeout(httpclient.DefaultDialTimeout) return builder } @@ -22,7 +23,8 @@ type jfrogHttpClientBuilder struct { preRequestInterceptors []PreRequestInterceptorFunc clientCertPath string clientCertKeyPath string - timeout time.Duration + dialTimeout time.Duration + overallRequestTimeout time.Duration httpClient *http.Client } @@ -66,8 +68,13 @@ func (builder *jfrogHttpClientBuilder) AppendPreRequestInterceptor(interceptor P return builder } -func (builder *jfrogHttpClientBuilder) SetTimeout(timeout time.Duration) *jfrogHttpClientBuilder { - builder.timeout = timeout +func (builder *jfrogHttpClientBuilder) SetDialTimeout(dialTimeout time.Duration) *jfrogHttpClientBuilder { + builder.dialTimeout = dialTimeout + return builder +} + +func (builder *jfrogHttpClientBuilder) SetOverallRequestTimeout(overallRequestTimeout time.Duration) *jfrogHttpClientBuilder { + builder.overallRequestTimeout = overallRequestTimeout return builder } @@ -84,7 +91,8 @@ func (builder *jfrogHttpClientBuilder) Build() (rtHttpClient *JfrogHttpClient, e SetClientCertPath(builder.clientCertPath). SetClientCertKeyPath(builder.clientCertKeyPath). SetContext(builder.ctx). - SetTimeout(builder.timeout). + SetDialTimeout(builder.dialTimeout). + SetOverallRequestTimeout(builder.overallRequestTimeout). SetRetries(builder.retries). SetRetryWaitMilliSecs(builder.retryWaitTimMilliSecs). SetHttpClient(builder.httpClient). diff --git a/lifecycle/manager.go b/lifecycle/manager.go index 7a5ebf0b4..e08ea15cc 100644 --- a/lifecycle/manager.go +++ b/lifecycle/manager.go @@ -23,6 +23,8 @@ func New(config config.Config) (*LifecycleServicesManager, error) { SetClientCertKeyPath(details.GetClientCertKeyPath()). AppendPreRequestInterceptor(details.RunPreRequestFunctions). SetContext(config.GetContext()). + SetDialTimeout(config.GetDialTimeout()). + SetOverallRequestTimeout(config.GetOverallRequestTimeout()). SetRetries(config.GetHttpRetries()). SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()). Build() diff --git a/pipelines/manager.go b/pipelines/manager.go index 9b9097363..f6f723b5a 100644 --- a/pipelines/manager.go +++ b/pipelines/manager.go @@ -22,6 +22,8 @@ func New(config config.Config) (*PipelinesServicesManager, error) { SetClientCertKeyPath(details.GetClientCertKeyPath()). AppendPreRequestInterceptor(details.RunPreRequestFunctions). SetContext(config.GetContext()). + SetDialTimeout(config.GetDialTimeout()). + SetOverallRequestTimeout(config.GetOverallRequestTimeout()). SetRetries(config.GetHttpRetries()). SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()). Build() diff --git a/tests/timeout_test.go b/tests/timeout_test.go new file mode 100644 index 000000000..660bdb611 --- /dev/null +++ b/tests/timeout_test.go @@ -0,0 +1,160 @@ +package tests + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/jfrog/jfrog-client-go/access" + accessAuth "github.com/jfrog/jfrog-client-go/access/auth" + "github.com/jfrog/jfrog-client-go/artifactory" + artifactoryAuth "github.com/jfrog/jfrog-client-go/artifactory/auth" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/config" + "github.com/jfrog/jfrog-client-go/distribution" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/pipelines" + + distributionAuth "github.com/jfrog/jfrog-client-go/distribution/auth" + distributionServices "github.com/jfrog/jfrog-client-go/distribution/services" + lifecycleAuth "github.com/jfrog/jfrog-client-go/lifecycle/auth" + lifecycleServices "github.com/jfrog/jfrog-client-go/lifecycle/services" + pipelinesAuth "github.com/jfrog/jfrog-client-go/pipelines/auth" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/jfrog/jfrog-client-go/xray" + xrayAuth "github.com/jfrog/jfrog-client-go/xray/auth" + "github.com/stretchr/testify/assert" +) + +const ( + overallRequestTimeout = time.Millisecond * 50 + handlerTimeout = time.Millisecond * 100 +) + +func TestTimeout(t *testing.T) { + previousLog := tests.RedirectLogOutputToNil() + defer func() { + log.SetLogger(previousLog) + }() + t.Run("testAccessTimeout", testAccessTimeout) + t.Run("testArtifactoryTimeout", testArtifactoryTimeout) + t.Run("testDistributionTimeout", testDistributionTimeout) + t.Run("testLifecycleTimeout", testLifecycleTimeout) + t.Run("testPipelinesTimeout", testPipelinesTimeout) + t.Run("testXrayTimeout", testXrayTimeout) +} + +func testAccessTimeout(t *testing.T) { + // Create mock server + url, cleanup := createSleepyRequestServer() + defer cleanup() + + // Create services manager configuring to work with the mock server + details := accessAuth.NewAccessDetails() + details.SetUrl(url) + servicesManager, err := access.New(createServiceConfigWithTimeout(t, details)) + assert.NoError(t, err) + + // Expect timeout + _, err = servicesManager.GetAllProjects() + assert.ErrorContains(t, err, "context deadline exceeded") +} + +func testArtifactoryTimeout(t *testing.T) { + // Create mock server + url, cleanup := createSleepyRequestServer() + defer cleanup() + + // Create services manager configuring to work with the mock server + details := artifactoryAuth.NewArtifactoryDetails() + details.SetUrl(url) + servicesManager, err := artifactory.New(createServiceConfigWithTimeout(t, details)) + assert.NoError(t, err) + + // Expect timeout + _, err = servicesManager.GetVersion() + assert.ErrorContains(t, err, "context deadline exceeded") +} + +func testDistributionTimeout(t *testing.T) { + // Create mock server + url, cleanup := createSleepyRequestServer() + defer cleanup() + + // Create services manager configuring to work with the mock server + details := distributionAuth.NewDistributionDetails() + details.SetUrl(url) + servicesManager, err := distribution.New(createServiceConfigWithTimeout(t, details)) + assert.NoError(t, err) + + // Expect timeout + _, err = servicesManager.GetDistributionStatus(distributionServices.DistributionStatusParams{}) + assert.ErrorContains(t, err, "context deadline exceeded") +} + +func testLifecycleTimeout(t *testing.T) { + // Create mock server + url, cleanup := createSleepyRequestServer() + defer cleanup() + + // Create services manager configuring to work with the mock server + details := lifecycleAuth.NewLifecycleDetails() + details.SetUrl(url) + servicesManager, err := lifecycle.New(createServiceConfigWithTimeout(t, details)) + assert.NoError(t, err) + + // Expect timeout + _, err = servicesManager.GetReleaseBundleCreationStatus(lifecycleServices.ReleaseBundleDetails{}, "", false) + assert.ErrorContains(t, err, "context deadline exceeded") +} + +func testPipelinesTimeout(t *testing.T) { + // Create mock server + url, cleanup := createSleepyRequestServer() + defer cleanup() + + // Create services manager configuring to work with the mock server + details := pipelinesAuth.NewPipelinesDetails() + details.SetUrl(url) + servicesManager, err := pipelines.New(createServiceConfigWithTimeout(t, details)) + assert.NoError(t, err) + + // Expect timeout + _, err = servicesManager.GetSystemInfo() + assert.ErrorContains(t, err, "context deadline exceeded") +} + +func testXrayTimeout(t *testing.T) { + // Create mock server + url, cleanup := createSleepyRequestServer() + defer cleanup() + + // Create services manager configuring to work with the mock server + details := xrayAuth.NewXrayDetails() + details.SetUrl(url) + servicesManager, err := xray.New(createServiceConfigWithTimeout(t, details)) + assert.NoError(t, err) + + // Expect timeout + _, err = servicesManager.GetVersion() + assert.ErrorContains(t, err, "context deadline exceeded") +} + +func createServiceConfigWithTimeout(t *testing.T, serverDetails auth.ServiceDetails) config.Config { + serviceConfig, err := config.NewConfigBuilder().SetOverallRequestTimeout(overallRequestTimeout).SetServiceDetails(serverDetails).Build() + assert.NoError(t, err) + return serviceConfig +} + +// Create a mock HTTP server that sleeps before responding to requests +func createSleepyRequestServer() (url string, cleanup func()) { + handler := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + time.Sleep(handlerTimeout) + }) + server := httptest.NewServer(handler) + url = server.URL + "/" + cleanup = server.Close + return +} diff --git a/utils/io/httputils/httpclient.go b/utils/io/httputils/httpclient.go index 5e1e13942..d44f0fc31 100644 --- a/utils/io/httputils/httpclient.go +++ b/utils/io/httputils/httpclient.go @@ -1,28 +1,38 @@ package httputils import ( - "github.com/jfrog/jfrog-client-go/utils" "net/http" "time" + + "github.com/jfrog/jfrog-client-go/utils" ) type HttpClientDetails struct { - User string - Password string - ApiKey string - AccessToken string - Headers map[string]string - Transport *http.Transport - HttpTimeout time.Duration + User string + Password string + ApiKey string + AccessToken string + Headers map[string]string + Transport *http.Transport + DialTimeout time.Duration + OverallRequestTimeout time.Duration } func (httpClientDetails HttpClientDetails) Clone() *HttpClientDetails { headers := make(map[string]string) utils.MergeMaps(httpClientDetails.Headers, headers) + var transport *http.Transport + if httpClientDetails.Transport != nil { + transport = httpClientDetails.Transport.Clone() + } return &HttpClientDetails{ - User: httpClientDetails.User, - Password: httpClientDetails.Password, - ApiKey: httpClientDetails.ApiKey, - AccessToken: httpClientDetails.AccessToken, - Headers: headers} + User: httpClientDetails.User, + Password: httpClientDetails.Password, + ApiKey: httpClientDetails.ApiKey, + AccessToken: httpClientDetails.AccessToken, + Headers: headers, + Transport: transport, + DialTimeout: httpClientDetails.DialTimeout, + OverallRequestTimeout: httpClientDetails.OverallRequestTimeout, + } } diff --git a/utils/tests/utils.go b/utils/tests/utils.go index 515c0a006..8f3993073 100644 --- a/utils/tests/utils.go +++ b/utils/tests/utils.go @@ -3,10 +3,7 @@ package tests import ( "bufio" "errors" - biutils "github.com/jfrog/build-info-go/utils" - "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - "github.com/jfrog/jfrog-client-go/utils/log" - "github.com/stretchr/testify/assert" + "io" "net" "net/http" "os" @@ -14,6 +11,11 @@ import ( "path/filepath" "strings" "testing" + + biutils "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/stretchr/testify/assert" ) type HttpServerHandlers map[string]func(w http.ResponseWriter, r *http.Request) @@ -184,3 +186,14 @@ func GetLocalArtifactoryTokenIfNeeded(url string) (adminToken string) { } return } + +// Set new logger with output redirection to a null logger. This is useful for negative tests. +// Caller is responsible to set the old log back. +func RedirectLogOutputToNil() (previousLog log.Log) { + previousLog = log.Logger + newLog := log.NewLogger(log.INFO, nil) + newLog.SetOutputWriter(io.Discard) + newLog.SetLogsWriter(io.Discard, 0) + log.SetLogger(newLog) + return previousLog +} diff --git a/xray/manager.go b/xray/manager.go index 06d185a5f..ac15b6e58 100644 --- a/xray/manager.go +++ b/xray/manager.go @@ -22,7 +22,8 @@ func New(config config.Config) (*XrayServicesManager, error) { SetCertificatesPath(config.GetCertificatesPath()). SetInsecureTls(config.IsInsecureTls()). SetContext(config.GetContext()). - SetTimeout(config.GetHttpTimeout()). + SetDialTimeout(config.GetDialTimeout()). + SetOverallRequestTimeout(config.GetOverallRequestTimeout()). SetClientCertPath(details.GetClientCertPath()). SetClientCertKeyPath(details.GetClientCertKeyPath()). AppendPreRequestInterceptor(details.RunPreRequestFunctions).