diff --git a/connection.go b/connection.go index a3e9bd5b7..7851c301b 100644 --- a/connection.go +++ b/connection.go @@ -14,10 +14,8 @@ import ( "io" "net/http" "net/url" - "os" "regexp" "strconv" - "strings" "sync" "sync/atomic" "time" @@ -67,8 +65,6 @@ const ( executionTypeStatement string = "statement" ) -const privateLinkSuffix = "privatelink.snowflakecomputing.com" - type snowflakeConn struct { ctx context.Context cfg *Config @@ -777,14 +773,8 @@ func buildSnowflakeConn(ctx context.Context, config Config) (*snowflakeConn, err // use the custom transport st = sc.cfg.Transporter } - if strings.HasSuffix(sc.cfg.Host, privateLinkSuffix) { - if err := sc.setupOCSPPrivatelink(sc.cfg.Application, sc.cfg.Host); err != nil { - return nil, err - } - } else { - if _, set := os.LookupEnv(cacheServerURLEnv); set { - os.Unsetenv(cacheServerURLEnv) - } + if err = setupOCSPEnvVars(sc.ctx, sc.cfg.Host); err != nil { + return nil, err } var tokenAccessor TokenAccessor if sc.cfg.TokenAccessor != nil { diff --git a/connection_test.go b/connection_test.go index 46a9c347a..ab4caad09 100644 --- a/connection_test.go +++ b/connection_test.go @@ -477,7 +477,29 @@ func fetchResultByQueryID( return nil } -func TestPrivateLink(t *testing.T) { +func TestIsPrivateLink(t *testing.T) { + for _, tc := range []struct { + host string + isPrivatelink bool + }{ + {"testaccount.us-east-1.snowflakecomputing.com", false}, + {"testaccount-no-privatelink.snowflakecomputing.com", false}, + {"testaccount.us-east-1.privatelink.snowflakecomputing.com", true}, + {"testaccount.cn-region.snowflakecomputing.cn", false}, + {"testaccount.cn-region.privaTELINk.snowflakecomputing.cn", true}, + {"testaccount.some-region.privatelink.snowflakecomputing.mil", true}, + {"testaccount.us-east-1.privatelink.snowflakecOMPUTING.com", true}, + {"snowhouse.snowflakecomputing.xyz", false}, + {"snowhouse.privatelink.snowflakecomputing.xyz", true}, + {"snowhouse.PRIVATELINK.snowflakecomputing.xyz", true}, + } { + t.Run(tc.host, func(t *testing.T) { + assertEqualE(t, isPrivateLink(tc.host), tc.isPrivatelink) + }) + } +} + +func TestBuildPrivatelinkConn(t *testing.T) { if _, err := buildSnowflakeConn(context.Background(), Config{ Account: "testaccount", User: "testuser", @@ -486,15 +508,70 @@ func TestPrivateLink(t *testing.T) { }); err != nil { t.Error(err) } + defer func() { + os.Unsetenv(cacheServerURLEnv) + os.Unsetenv(ocspRetryURLEnv) + }() + ocspURL := os.Getenv(cacheServerURLEnv) - expectedURL := "http://ocsp.testaccount.us-east-1.privatelink.snowflakecomputing.com/ocsp_response_cache.json" - if ocspURL != expectedURL { - t.Errorf("expected: %v, got: %v", expectedURL, ocspURL) - } + assertEqualE(t, ocspURL, "http://ocsp.testaccount.us-east-1.privatelink.snowflakecomputing.com/ocsp_response_cache.json") retryURL := os.Getenv(ocspRetryURLEnv) - expectedURL = "http://ocsp.testaccount.us-east-1.privatelink.snowflakecomputing.com/retry/%v/%v" - if retryURL != expectedURL { - t.Errorf("expected: %v, got: %v", expectedURL, retryURL) + assertEqualE(t, retryURL, "http://ocsp.testaccount.us-east-1.privatelink.snowflakecomputing.com/retry/%v/%v") +} + +func TestOcspEnvVarsSetup(t *testing.T) { + ctx := context.Background() + for _, tc := range []struct { + host string + cacheURL string + privateLinkRetryURL string + }{ + { + host: "testaccount.us-east-1.snowflakecomputing.com", + cacheURL: "", // no privatelink, default ocsp cache URL, no need to setup env vars + privateLinkRetryURL: "", + }, + { + host: "testaccount-no-privatelink.snowflakecomputing.com", + cacheURL: "", // no privatelink, default ocsp cache URL, no need to setup env vars + privateLinkRetryURL: "", + }, + { + host: "testaccount.us-east-1.privatelink.snowflakecomputing.com", + cacheURL: "http://ocsp.testaccount.us-east-1.privatelink.snowflakecomputing.com/ocsp_response_cache.json", + privateLinkRetryURL: "http://ocsp.testaccount.us-east-1.privatelink.snowflakecomputing.com/retry/%v/%v", + }, + { + host: "testaccount.cn-region.snowflakecomputing.cn", + cacheURL: "http://ocsp.testaccount.cn-region.snowflakecomputing.cn/ocsp_response_cache.json", + privateLinkRetryURL: "", // not a privatelink env, no need to setup retry URL + }, + { + host: "testaccount.cn-region.privaTELINk.snowflakecomputing.cn", + cacheURL: "http://ocsp.testaccount.cn-region.privatelink.snowflakecomputing.cn/ocsp_response_cache.json", + privateLinkRetryURL: "http://ocsp.testaccount.cn-region.privatelink.snowflakecomputing.cn/retry/%v/%v", + }, + { + host: "testaccount.some-region.privatelink.snowflakecomputing.mil", + cacheURL: "http://ocsp.testaccount.some-region.privatelink.snowflakecomputing.mil/ocsp_response_cache.json", + privateLinkRetryURL: "http://ocsp.testaccount.some-region.privatelink.snowflakecomputing.mil/retry/%v/%v", + }, + } { + t.Run(tc.host, func(t *testing.T) { + if err := setupOCSPEnvVars(ctx, tc.host); err != nil { + t.Errorf("error during OCSP env vars setup; %v", err) + } + defer func() { + os.Unsetenv(cacheServerURLEnv) + os.Unsetenv(ocspRetryURLEnv) + }() + + cacheURLFromEnv := os.Getenv(cacheServerURLEnv) + assertEqualE(t, cacheURLFromEnv, tc.cacheURL) + retryURL := os.Getenv(ocspRetryURLEnv) + assertEqualE(t, retryURL, tc.privateLinkRetryURL) + + }) } } diff --git a/connection_util.go b/connection_util.go index 29a526075..167223558 100644 --- a/connection_util.go +++ b/connection_util.go @@ -288,20 +288,49 @@ func populateChunkDownloader( } } -func (sc *snowflakeConn) setupOCSPPrivatelink(app string, host string) error { - ocspCacheServer := fmt.Sprintf("http://ocsp.%v/ocsp_response_cache.json", host) - logger.WithContext(sc.ctx).Debugf("OCSP Cache Server for Privatelink: %v\n", ocspCacheServer) +func setupOCSPEnvVars(ctx context.Context, host string) error { + host = strings.ToLower(host) + if isPrivateLink(host) { + if err := setupOCSPPrivatelink(ctx, host); err != nil { + return err + } + } else if !strings.HasSuffix(host, defaultDomain) { + ocspCacheServer := fmt.Sprintf("http://ocsp.%v/%v", host, cacheFileBaseName) + logger.WithContext(ctx).Debugf("OCSP Cache Server for %v: %v\n", host, ocspCacheServer) + if err := os.Setenv(cacheServerURLEnv, ocspCacheServer); err != nil { + return err + } + } else { + if _, set := os.LookupEnv(cacheServerURLEnv); set { + os.Unsetenv(cacheServerURLEnv) + } + } + return nil +} + +func setupOCSPPrivatelink(ctx context.Context, host string) error { + ocspCacheServer := fmt.Sprintf("http://ocsp.%v/%v", host, cacheFileBaseName) + logger.WithContext(ctx).Debugf("OCSP Cache Server for Privatelink: %v\n", ocspCacheServer) if err := os.Setenv(cacheServerURLEnv, ocspCacheServer); err != nil { return err } ocspRetryHostTemplate := fmt.Sprintf("http://ocsp.%v/retry/", host) + "%v/%v" - logger.WithContext(sc.ctx).Debugf("OCSP Retry URL for Privatelink: %v\n", ocspRetryHostTemplate) + logger.WithContext(ctx).Debugf("OCSP Retry URL for Privatelink: %v\n", ocspRetryHostTemplate) if err := os.Setenv(ocspRetryURLEnv, ocspRetryHostTemplate); err != nil { return err } return nil } +/** + * We can only tell if private link is enabled for certain hosts when the hostname contains the subdomain + * 'privatelink.snowflakecomputing.' but we don't have a good way of telling if a private link connection is + * expected for internal stages for example. + */ +func isPrivateLink(host string) bool { + return strings.Contains(strings.ToLower(host), ".privatelink.snowflakecomputing.") +} + func isStatementContext(ctx context.Context) bool { v := ctx.Value(executionType) return v == executionTypeStatement diff --git a/driver.go b/driver.go index 263a1394a..bd99d110e 100644 --- a/driver.go +++ b/driver.go @@ -8,6 +8,7 @@ import ( "database/sql/driver" "os" "runtime" + "strings" "sync" ) @@ -41,6 +42,12 @@ func (d SnowflakeDriver) OpenWithConfig(ctx context.Context, config Config) (dri return nil, err } + if strings.HasSuffix(strings.ToLower(config.Host), cnDomain) { + logger.WithContext(ctx).Info("Connecting to CHINA Snowflake domain") + } else { + logger.WithContext(ctx).Info("Connecting to GLOBAL Snowflake domain") + } + if err = authenticateWithConfig(sc); err != nil { return nil, err } diff --git a/dsn.go b/dsn.go index 5f61b5b04..797ddba5b 100644 --- a/dsn.go +++ b/dsn.go @@ -27,6 +27,8 @@ const ( defaultExternalBrowserTimeout = 120 * time.Second // Timeout for external browser login defaultMaxRetryCount = 7 // specifies maximum number of subsequent retries defaultDomain = ".snowflakecomputing.com" + cnDomain = ".snowflakecomputing.cn" + topLevelDomainPrefix = ".snowflakecomputing." // used to extract the domain from host ) // ConfigBool is a type to represent true or false in the Config @@ -135,26 +137,26 @@ func (c *Config) ocspMode() string { // DSN constructs a DSN for Snowflake db. func DSN(cfg *Config) (dsn string, err error) { + if cfg.Region == "us-west-2" { + cfg.Region = "" + } + // in case account includes region + region, posDot := extractRegionFromAccount(cfg.Account) + if region != "" { + if cfg.Region != "" { + return "", errRegionConflict() + } + cfg.Region = region + cfg.Account = cfg.Account[:posDot] + } hasHost := true if cfg.Host == "" { hasHost = false - if cfg.Region == "us-west-2" { - cfg.Region = "" - } if cfg.Region == "" { cfg.Host = cfg.Account + defaultDomain } else { - cfg.Host = cfg.Account + "." + cfg.Region + defaultDomain - } - } - // in case account includes region - posDot := strings.Index(cfg.Account, ".") - if posDot > 0 { - if cfg.Region != "" { - return "", errInvalidRegion() + cfg.Host = buildHostFromAccountAndRegion(cfg.Account, cfg.Region) } - cfg.Region = cfg.Account[posDot+1:] - cfg.Account = cfg.Account[:posDot] } err = fillMissingConfigParameters(cfg) if err != nil { @@ -374,7 +376,7 @@ func ParseDSN(dsn string) (cfg *Config, err error) { return } } - if cfg.Account == "" && strings.HasSuffix(cfg.Host, defaultDomain) { + if cfg.Account == "" && hostIncludesTopLevelDomain(cfg.Host) { posDot := strings.Index(cfg.Host, ".") if posDot > 0 { cfg.Account = cfg.Host[:posDot] @@ -428,7 +430,7 @@ func ParseDSN(dsn string) (cfg *Config, err error) { func fillMissingConfigParameters(cfg *Config) error { posDash := strings.LastIndex(cfg.Account, "-") if posDash > 0 { - if strings.Contains(cfg.Host, ".global.") { + if strings.Contains(strings.ToLower(cfg.Host), ".global.") { cfg.Account = cfg.Account[:posDash] } } @@ -453,19 +455,24 @@ func fillMissingConfigParameters(cfg *Config) error { cfg.Region = strings.Trim(cfg.Region, " ") if cfg.Region != "" { // region is specified but not included in Host - i := strings.Index(cfg.Host, defaultDomain) + domain, i := extractDomainFromHost(cfg.Host) if i >= 1 { hostPrefix := cfg.Host[0:i] if !strings.HasSuffix(hostPrefix, cfg.Region) { - cfg.Host = hostPrefix + "." + cfg.Region + defaultDomain + cfg.Host = fmt.Sprintf("%v.%v%v", hostPrefix, cfg.Region, domain) } } } if cfg.Host == "" { if cfg.Region != "" { - cfg.Host = cfg.Account + "." + cfg.Region + defaultDomain + cfg.Host = cfg.Account + "." + cfg.Region + getDomainBasedOnRegion(cfg.Region) } else { - cfg.Host = cfg.Account + defaultDomain + region, _ := extractRegionFromAccount(cfg.Account) + if region != "" { + cfg.Host = cfg.Account + getDomainBasedOnRegion(region) + } else { + cfg.Host = cfg.Account + defaultDomain + } } } if cfg.LoginTimeout == 0 { @@ -505,7 +512,8 @@ func fillMissingConfigParameters(cfg *Config) error { cfg.IncludeRetryReason = ConfigBoolTrue } - if strings.HasSuffix(cfg.Host, defaultDomain) && len(cfg.Host) == len(defaultDomain) { + domain, _ := extractDomainFromHost(cfg.Host) + if len(cfg.Host) == len(domain) { return &SnowflakeError{ Number: ErrCodeFailedToParseHost, Message: errMsgFailedToParseHost, @@ -515,6 +523,38 @@ func fillMissingConfigParameters(cfg *Config) error { return nil } +func extractDomainFromHost(host string) (domain string, index int) { + i := strings.LastIndex(strings.ToLower(host), topLevelDomainPrefix) + if i >= 1 { + domain = host[i:] + return domain, i + } + return "", i +} + +func getDomainBasedOnRegion(region string) string { + if strings.HasPrefix(strings.ToLower(region), "cn-") { + return cnDomain + } + return defaultDomain +} + +func extractRegionFromAccount(account string) (region string, posDot int) { + posDot = strings.Index(strings.ToLower(account), ".") + if posDot > 0 { + return account[posDot+1:], posDot + } + return "", posDot +} + +func hostIncludesTopLevelDomain(host string) bool { + return strings.Contains(strings.ToLower(host), topLevelDomainPrefix) +} + +func buildHostFromAccountAndRegion(account, region string) string { + return account + "." + region + getDomainBasedOnRegion(region) +} + func authRequiresUser(cfg *Config) bool { return cfg.Authenticator != AuthTypeOAuth && cfg.Authenticator != AuthTypeTokenAccessor && @@ -528,18 +568,20 @@ func authRequiresPassword(cfg *Config) bool { cfg.Authenticator != AuthTypeJwt } -// transformAccountToHost transforms host to account name +// transformAccountToHost transforms account to host func transformAccountToHost(cfg *Config) (err error) { - if cfg.Port == 0 && !strings.HasSuffix(cfg.Host, defaultDomain) && cfg.Host != "" { + if cfg.Port == 0 && cfg.Host != "" && !hostIncludesTopLevelDomain(cfg.Host) { // account name is specified instead of host:port cfg.Account = cfg.Host - cfg.Host = cfg.Account + defaultDomain - cfg.Port = 443 - posDot := strings.Index(cfg.Account, ".") - if posDot > 0 { - cfg.Region = cfg.Account[posDot+1:] + region, posDot := extractRegionFromAccount(cfg.Account) + if region != "" { + cfg.Region = region cfg.Account = cfg.Account[:posDot] + cfg.Host = buildHostFromAccountAndRegion(cfg.Account, cfg.Region) + } else { + cfg.Host = cfg.Account + defaultDomain } + cfg.Port = 443 } return nil } diff --git a/dsn_test.go b/dsn_test.go index c3d2c40fd..3563434ae 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -93,6 +93,21 @@ func TestParseDSN(t *testing.T) { ocspMode: ocspModeFailOpen, err: nil, }, + { + dsn: "u:p@/db?account=ac®ion=cn-region", + config: &Config{ + Account: "ac", User: "u", Password: "p", Database: "db", Region: "cn-region", + Protocol: "https", Host: "ac.cn-region.snowflakecomputing.cn", Port: 443, + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, { dsn: "user:pass@account-hfdw89q748ew9gqf48w9qgf.global/db/s", config: &Config{ @@ -140,6 +155,21 @@ func TestParseDSN(t *testing.T) { ocspMode: ocspModeFailOpen, err: nil, }, + { + dsn: "user:pass@account.cn-region", + config: &Config{ + Account: "account", User: "user", Password: "pass", Region: "cn-region", + Protocol: "https", Host: "account.cn-region.snowflakecomputing.cn", Port: 443, + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, { dsn: "user:pass@account.eu-faraway", config: &Config{ @@ -266,6 +296,102 @@ func TestParseDSN(t *testing.T) { ocspMode: ocspModeFailOpen, err: nil, }, + { + dsn: "u:p@a.snowflakecomputing.mil/db/pa?account=a", + config: &Config{ + Account: "a", User: "u", Password: "p", Region: "", + Protocol: "https", Host: "a.snowflakecomputing.mil", Port: 443, + Database: "db", Schema: "pa", + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, + { + dsn: "u:p@a.eu-faraway.snowflakecomputing.mil/db/pa?account=a®ion=eu-faraway", + config: &Config{ + Account: "a", User: "u", Password: "p", Region: "eu-faraway", + Protocol: "https", Host: "a.eu-faraway.snowflakecomputing.mil", Port: 443, + Database: "db", Schema: "pa", + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, + { + dsn: "u:p@a.snowflakecomputing.gov.pl/db/pa?account=a", + config: &Config{ + Account: "a", User: "u", Password: "p", Region: "", + Protocol: "https", Host: "a.snowflakecomputing.gov.pl", Port: 443, + Database: "db", Schema: "pa", + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, + { + dsn: "u:p@a.snowflakecomputing.cn/db/pa?account=a", + config: &Config{ + Account: "a", User: "u", Password: "p", Region: "", + Protocol: "https", Host: "a.snowflakecomputing.cn", Port: 443, + Database: "db", Schema: "pa", + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, + { + dsn: "u:p@a.cn-region.snowflakecomputing.mil/db/pa?account=a®ion=cn-region", + config: &Config{ + Account: "a", User: "u", Password: "p", Region: "cn-region", + Protocol: "https", Host: "a.cn-region.snowflakecomputing.mil", Port: 443, + Database: "db", Schema: "pa", + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, + { + dsn: "u:p@a.cn-region.snowflakecomputing.cn/db/pa?account=a®ion=cn-region&protocol=https&role=r&timezone=UTC&warehouse=w", + config: &Config{ + Account: "a", User: "u", Password: "p", Region: "cn-region", + Protocol: "https", Host: "a.cn-region.snowflakecomputing.cn", Port: 443, + Database: "db", Schema: "pa", Role: "r", Warehouse: "w", + OCSPFailOpen: OCSPFailOpenTrue, + ValidateDefaultParameters: ConfigBoolTrue, + ClientTimeout: defaultClientTimeout, + JWTClientTimeout: defaultJWTClientTimeout, + ExternalBrowserTimeout: defaultExternalBrowserTimeout, + IncludeRetryReason: ConfigBoolTrue, + }, + ocspMode: ocspModeFailOpen, + err: nil, + }, { dsn: "u:p@snowflake.local:9876?account=a&protocol=http", config: &Config{ @@ -1005,6 +1131,50 @@ func TestDSN(t *testing.T) { }, dsn: "u:p@a-aofnadsf.global.snowflakecomputing.com:443?ocspFailOpen=true®ion=global&validateDefaultParameters=true", }, + { + cfg: &Config{ + User: "u", + Password: "p", + Account: "account-name", + Region: "cn-region", + }, + dsn: "u:p@account-name.cn-region.snowflakecomputing.cn:443?ocspFailOpen=true®ion=cn-region&validateDefaultParameters=true", + }, + { + cfg: &Config{ + User: "u", + Password: "p", + Account: "account-name.cn-region", + }, + dsn: "u:p@account-name.cn-region.snowflakecomputing.cn:443?ocspFailOpen=true®ion=cn-region&validateDefaultParameters=true", + }, + { + cfg: &Config{ + User: "u", + Password: "p", + Account: "account-name.cn-region", + Host: "account-name.cn-region.snowflakecomputing.cn", + }, + dsn: "u:p@account-name.cn-region.snowflakecomputing.cn:443?account=account-name&ocspFailOpen=true®ion=cn-region&validateDefaultParameters=true", + }, + { + cfg: &Config{ + User: "u", + Password: "p", + Account: "account-name", + Host: "account-name.snowflakecomputing.mil", + }, + dsn: "u:p@account-name.snowflakecomputing.mil:443?account=account-name&ocspFailOpen=true&validateDefaultParameters=true", + }, + { + cfg: &Config{ + User: "u", + Password: "p", + Account: "account-name", + Host: "account-name.snowflakecomputing.gov.pl", + }, + dsn: "u:p@account-name.snowflakecomputing.gov.pl:443?account=account-name&ocspFailOpen=true&validateDefaultParameters=true", + }, { cfg: &Config{ User: "u", @@ -1012,7 +1182,7 @@ func TestDSN(t *testing.T) { Account: "a-aofnadsf.global", Region: "r", }, - err: errInvalidRegion(), + err: errRegionConflict(), }, { cfg: &Config{ @@ -1098,7 +1268,7 @@ func TestDSN(t *testing.T) { Account: "a.e", Region: "r", }, - err: errInvalidRegion(), + err: errRegionConflict(), }, { cfg: &Config{ @@ -1226,6 +1396,14 @@ func TestDSN(t *testing.T) { }, dsn: "u:p@a.b.c.snowflakecomputing.com:443?ocspFailOpen=true®ion=b.c&validateDefaultParameters=true", }, + { + cfg: &Config{ + User: "u", + Password: "p", + Account: "account.snowflakecomputing.com", + }, + dsn: "u:p@account.snowflakecomputing.com.snowflakecomputing.com:443?ocspFailOpen=true®ion=snowflakecomputing.com&validateDefaultParameters=true", + }, { cfg: &Config{ User: "u", @@ -1242,7 +1420,7 @@ func TestDSN(t *testing.T) { Account: "a.b.c", Region: "r", }, - err: errInvalidRegion(), + err: errRegionConflict(), }, { cfg: &Config{ diff --git a/errors.go b/errors.go index 73543ac30..4d068e3b2 100644 --- a/errors.go +++ b/errors.go @@ -320,7 +320,7 @@ func errEmptyPassword() *SnowflakeError { } // Returned if a DSN's implicit region from account parameter and explicit region parameter conflict. -func errInvalidRegion() *SnowflakeError { +func errRegionConflict() *SnowflakeError { return &SnowflakeError{ Number: ErrCodeRegionOverlap, Message: "two regions specified", diff --git a/ocsp.go b/ocsp.go index 452451981..944bf00f1 100644 --- a/ocsp.go +++ b/ocsp.go @@ -88,7 +88,7 @@ const ( cacheFileBaseName = "ocsp_response_cache.json" // cacheExpire specifies cache data expiration time in seconds. cacheExpire = float64(24 * 60 * 60) - cacheServerURL = "http://ocsp.snowflakecomputing.com" + defaultCacheServerURL = "http://ocsp.snowflakecomputing.com" cacheServerEnabledEnv = "SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED" cacheServerURLEnv = "SF_OCSP_RESPONSE_CACHE_SERVER_URL" cacheDirEnv = "SF_OCSP_RESPONSE_CACHE_DIR" @@ -772,7 +772,7 @@ func downloadOCSPCacheServer() { } ocspCacheServerURL := os.Getenv(cacheServerURLEnv) if ocspCacheServerURL == "" { - ocspCacheServerURL = fmt.Sprintf("%v/%v", cacheServerURL, cacheFileBaseName) + ocspCacheServerURL = fmt.Sprintf("%v/%v", defaultCacheServerURL, cacheFileBaseName) } u, err := url.Parse(ocspCacheServerURL) if err != nil {