diff --git a/codefresh/cfclient/client.go b/codefresh/cfclient/client.go index f63371e..e3e6ed7 100644 --- a/codefresh/cfclient/client.go +++ b/codefresh/cfclient/client.go @@ -7,6 +7,9 @@ import ( "io" "net/http" "strings" + "time" + + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/envutil" ) // Client token, host, htpp.Client @@ -28,19 +31,41 @@ type RequestOptions struct { XAccessToken string } -// NewClient returns a new client configured to communicate on a server with the -// given hostname and to send an Authorization Header with the value of -// token -func NewClient(hostname string, hostnameV2 string, token string, tokenHeader string) *Client { +// HttpClient returns a client which can be configured to communicate on a server with custom timeout settings +func NewHttpClient(hostname string, hostnameV2 string, token string, tokenHeader string) *Client { if tokenHeader == "" { tokenHeader = "Authorization" } + + // Configurable HTTP transport with proper connection pooling and timeouts to prevent "connection reset by peer" errors, + // default values are equivalent to default &http.Client{} settings + transport := &http.Transport{ + // Limit maximum idle connections per host to prevent connection exhaustion + MaxIdleConnsPerHost: envutil.GetEnvAsInt("CF_HTTP_MAX_IDLE_CONNECTIONS_PER_HOST", 2), + // Limit total idle connections + MaxIdleConns: envutil.GetEnvAsInt("CF_HTTP_MAX_IDLE_CONNECTIONS", 100), + // Close idle connections after specified seconds to prevent server-side timeouts + IdleConnTimeout: time.Duration(envutil.GetEnvAsInt("CF_HTTP_IDLE_CONNECTION_TIMEOUT", 90)) * time.Second, + // Timeout for TLS handshake in seconds + TLSHandshakeTimeout: time.Duration(envutil.GetEnvAsInt("CF_HTTP_TLS_HANDSHAKE_TIMEOUT", 10)) * time.Second, + // Timeout for expecting response headers in seconds, 0 - no limits + ResponseHeaderTimeout: time.Duration(envutil.GetEnvAsInt("CF_HTTP_RESPONSE_HEADER_TIMEOUT", 0)) * time.Second, + // Disable connection reuse for more stable connections + DisableKeepAlives: envutil.GetEnvAsBool("CF_HTTP_DISABLE_KEEPALIVES", false), + } + + httpClient := &http.Client{ + Transport: transport, + // Overall request timeout in seconds, 0 - no limits + Timeout: time.Duration(envutil.GetEnvAsInt("CF_HTTP_GLOBAL_TIMEOUT", 0)) * time.Second, + } + return &Client{ Host: hostname, HostV2: hostnameV2, Token: token, TokenHeader: tokenHeader, - Client: &http.Client{}, + Client: httpClient, featureFlags: map[string]bool{}, } diff --git a/codefresh/cfclient/gql_client.go b/codefresh/cfclient/gql_client.go index a887668..64c86e2 100644 --- a/codefresh/cfclient/gql_client.go +++ b/codefresh/cfclient/gql_client.go @@ -37,8 +37,7 @@ func (client *Client) SendGqlRequest(request GraphQLRequest) ([]byte, error) { req.Header.Set(tokenHeader, client.Token) req.Header.Set("Content-Type", "application/json; charset=utf-8") - httpClient := &http.Client{} - resp, err := httpClient.Do(req) + resp, err := client.Client.Do(req) if err != nil { return nil, err } diff --git a/codefresh/cfclient/user.go b/codefresh/cfclient/user.go index 5f3827f..c1ff4a7 100644 --- a/codefresh/cfclient/user.go +++ b/codefresh/cfclient/user.go @@ -144,7 +144,7 @@ func (client *Client) AddUserToTeamByAdmin(userID string, accountID string, team return err } // new Client for accountAdmin - accountAdminClient := NewClient(client.Host, "", accountAdminToken, "x-access-token") + accountAdminClient := NewHttpClient(client.Host, "", accountAdminToken, "x-access-token") usersTeam, err := accountAdminClient.GetTeamByName(team) if err != nil { return err diff --git a/codefresh/envutil/env.go b/codefresh/envutil/env.go new file mode 100644 index 0000000..33f9322 --- /dev/null +++ b/codefresh/envutil/env.go @@ -0,0 +1,35 @@ +// Package envutil provides utility functions for working with environment variables. +package envutil + +import ( + "os" + "strconv" +) + +// GetEnvAsString retrieves an environment variable as a string, returning the default value if not set +func GetEnvAsString(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +// GetEnvAsInt retrieves an environment variable as an integer, returning the default value if not set or invalid +func GetEnvAsInt(key string, defaultValue int) int { + if value := os.Getenv(key); value != "" { + if intValue, err := strconv.Atoi(value); err == nil { + return intValue + } + } + return defaultValue +} + +// GetEnvAsBool retrieves an environment variable as a boolean, returning the default value if not set or invalid +func GetEnvAsBool(key string, defaultValue bool) bool { + if value := os.Getenv(key); value != "" { + if boolValue, err := strconv.ParseBool(value); err == nil { + return boolValue + } + } + return defaultValue +} diff --git a/codefresh/provider.go b/codefresh/provider.go index be0c132..53a1e00 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -3,9 +3,10 @@ package codefresh import ( "fmt" + "os" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "os" ) func Provider() *schema.Provider { @@ -91,5 +92,5 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) { token = os.Getenv(ENV_CODEFRESH_API_KEY) } - return cfclient.NewClient(apiURL, apiURLV2, token, ""), nil + return cfclient.NewHttpClient(apiURL, apiURLV2, token, ""), nil } diff --git a/main.go b/main.go index f1e039d..e032c34 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "os" "github.com/codefresh-io/terraform-provider-codefresh/codefresh" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/envutil" "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" ) @@ -12,10 +13,7 @@ import ( func main() { debugMode := (os.Getenv(codefresh.ENV_CODEFRESH_PLUGIN_DEBUG) != "") - providerAddr := os.Getenv(codefresh.ENV_CODEFRESH_PLUGIN_ADDR) - if providerAddr == "" { - providerAddr = codefresh.DEFAULT_CODEFRESH_PLUGIN_ADDR - } + providerAddr := envutil.GetEnvAsString(codefresh.ENV_CODEFRESH_PLUGIN_ADDR, codefresh.DEFAULT_CODEFRESH_PLUGIN_ADDR) plugin.Serve(&plugin.ServeOpts{ ProviderAddr: providerAddr, ProviderFunc: codefresh.Provider,