From c0109994b90cacfdb2f9a863a91e34f7c95322e7 Mon Sep 17 00:00:00 2001 From: Serhii Dimchenko Date: Thu, 8 Sep 2022 12:40:42 +0300 Subject: [PATCH 1/3] Redshift serverless support --- redshift/config.go | 1 + redshift/provider.go | 7 ++++++ redshift/resource_redshift_schema.go | 27 ++++++++++------------- redshift/resource_redshift_user.go | 33 ++++++++++++++++++---------- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/redshift/config.go b/redshift/config.go index 9519fc0..ec2b794 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -24,6 +24,7 @@ type Config struct { Database string SSLMode string MaxConns int + IsServerless bool } // Client struct holding connection string diff --git a/redshift/provider.go b/redshift/provider.go index b9a1c87..c4c9ff6 100644 --- a/redshift/provider.go +++ b/redshift/provider.go @@ -125,6 +125,12 @@ func Provider() *schema.Provider { }, }, }, + "is_serverless": { + Type: schema.TypeBool, + Description: "Flag if redshift is serverless.", + Optional: true, + Default: false, + }, }, ResourcesMap: map[string]*schema.Resource{ "redshift_user": redshiftUser(), @@ -160,6 +166,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { Database: d.Get("database").(string), SSLMode: d.Get("sslmode").(string), MaxConns: d.Get("max_connections").(int), + IsServerless: d.Get("is_serverless").(bool), } log.Println("[DEBUG] creating database client") diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index ae1378f..6b868be 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -449,24 +449,21 @@ func resourceRedshiftSchemaReadImpl(db *DBConnection, d *schema.ResourceData) er } func resourceRedshiftSchemaReadLocal(db *DBConnection, d *schema.ResourceData) error { - var schemaQuota int - - err := db.QueryRow(` - SELECT - COALESCE(quota, 0) - FROM svv_schema_quota_state - WHERE schema_id = $1 - `, d.Id()).Scan(&schemaQuota) - switch { - case err == sql.ErrNoRows: - schemaQuota = 0 - case err != nil: - return err + var schemaQuota int = 0 + if !db.client.config.IsServerless { + err := db.QueryRow(` + SELECT + COALESCE(quota, 0) + FROM svv_schema_quota_state + WHERE schema_id = $1 + `, d.Id()).Scan(&schemaQuota) + if err != nil && err != sql.ErrNoRows { + return err + } } - d.Set(schemaQuotaAttr, schemaQuota) d.Set(schemaExternalSchemaAttr, nil) - + return nil } diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index 731a5f4..a142c66 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -270,16 +270,28 @@ func resourceRedshiftUserRead(db *DBConnection, d *schema.ResourceData) error { } func resourceRedshiftUserReadImpl(db *DBConnection, d *schema.ResourceData) error { - var userName, userValidUntil, userConnLimit, userSyslogAccess, userSessionTimeout string + var userName, userValidUntil, userConnLimit, userSyslogAccess, userInfoTable string var userSuperuser, userCreateDB bool - - columns := []string{ - "usename", - "usecreatedb", - "usesuper", - "syslogaccess", - `COALESCE(useconnlimit::TEXT, 'UNLIMITED')`, - "sessiontimeout", + var columns []string + + if db.client.config.IsServerless { + columns = []string{ + "usename", + "usecreatedb", + "usesuper", + "'RESTRICTED'", + "'UNLIMITED'", + } + userInfoTable = "pg_user" + } else { + columns = []string{ + "usename", + "usecreatedb", + "usesuper", + "syslogaccess", + `COALESCE(useconnlimit::TEXT, 'UNLIMITED')`, + } + userInfoTable = "svl_user_info" } values := []interface{}{ @@ -288,12 +300,11 @@ func resourceRedshiftUserReadImpl(db *DBConnection, d *schema.ResourceData) erro &userSuperuser, &userSyslogAccess, &userConnLimit, - &userSessionTimeout, } useSysID := d.Id() - userSQL := fmt.Sprintf("SELECT %s FROM svl_user_info WHERE usesysid = $1", strings.Join(columns, ",")) + userSQL := fmt.Sprintf("SELECT %s FROM %s WHERE usesysid = $1", strings.Join(columns, ","), userInfoTable) err := db.QueryRow(userSQL, useSysID).Scan(values...) switch { case err == sql.ErrNoRows: From 96cc03952b16e2e187177c1c252bb609b888ca26 Mon Sep 17 00:00:00 2001 From: svdimchenko Date: Tue, 13 Sep 2022 09:31:33 +0300 Subject: [PATCH 2/3] Generated docs --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 79f59aa..57b83aa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -56,6 +56,7 @@ provider "redshift" { - **database** (String) The name of the database to connect to. The default is `redshift`. - **host** (String) Name of Redshift server address to connect to. +- **is_serverless** (Boolean) Flag if redshift is serverless. - **max_connections** (Number) Maximum number of connections to establish to the database. Zero means unlimited. - **password** (String, Sensitive) Password to be used if the Redshift server demands password authentication. - **port** (Number) The Redshift port number to connect to at the server host. From eab09463a0102bf6180aec3c63bfcc2d67d2c904 Mon Sep 17 00:00:00 2001 From: Wojciech Inglot Date: Fri, 20 Jan 2023 15:20:44 +0100 Subject: [PATCH 3/3] Support for Redshift Serverless --- docs/index.md | 1 - redshift/config.go | 27 ++++++++++++++++++++++- redshift/helpers.go | 6 +++++ redshift/provider.go | 7 ------ redshift/resource_redshift_schema.go | 9 ++++++-- redshift/resource_redshift_user.go | 33 ++++++++++------------------ 6 files changed, 50 insertions(+), 33 deletions(-) diff --git a/docs/index.md b/docs/index.md index 57b83aa..79f59aa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -56,7 +56,6 @@ provider "redshift" { - **database** (String) The name of the database to connect to. The default is `redshift`. - **host** (String) Name of Redshift server address to connect to. -- **is_serverless** (Boolean) Flag if redshift is serverless. - **max_connections** (Number) Maximum number of connections to establish to the database. Zero means unlimited. - **password** (String, Sensitive) Password to be used if the Redshift server demands password authentication. - **port** (Number) The Redshift port number to connect to at the server host. diff --git a/redshift/config.go b/redshift/config.go index ec2b794..8ac7098 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -24,7 +24,9 @@ type Config struct { Database string SSLMode string MaxConns int - IsServerless bool + + isServerless bool + checkedForServerless bool } // Client struct holding connection string @@ -49,6 +51,29 @@ func (c *Config) NewClient(database string) *Client { } } +func (c *Config) IsServerless(db *DBConnection) (bool, error) { + if c.checkedForServerless { + return c.isServerless, nil + } + + c.checkedForServerless = true + + _, err := db.Query("SELECT 1 FROM SYS_SERVERLESS_USAGE") + // No error means we have accessed the view and are running Redshift Serverless + if err == nil { + c.isServerless = true + return true, nil + } + + // Insuficcient privileges means we do not have access to this view ergo we run on Redshift classic + if isPqErrorWithCode(err, pgErrorCodeInsufficientPrivileges) { + c.isServerless = false + return false, nil + } + + return false, err +} + // Connect returns a copy to an sql.Open()'ed database connection wrapped in a DBConnection struct. // Callers must return their database resources. Use of QueryRow() or Exec() is encouraged. // Query() must have their rows.Close()'ed. diff --git a/redshift/helpers.go b/redshift/helpers.go index f8df5dc..8bc0d8e 100644 --- a/redshift/helpers.go +++ b/redshift/helpers.go @@ -18,6 +18,8 @@ const ( pqErrorCodeDeadlock = "40P01" pqErrorCodeFailedTransaction = "25P02" pqErrorCodeDuplicateSchema = "42P06" + + pgErrorCodeInsufficientPrivileges = "42501" ) // startTransaction starts a new DB transaction on the specified database. @@ -134,6 +136,10 @@ func isRetryablePQError(code string) bool { return ok } +func isPqErrorWithCode(err error, code string) bool { + return string(err.(*pq.Error).Code) == code +} + func splitCsvAndTrim(raw string) ([]string, error) { if raw == "" { return []string{}, nil diff --git a/redshift/provider.go b/redshift/provider.go index c4c9ff6..b9a1c87 100644 --- a/redshift/provider.go +++ b/redshift/provider.go @@ -125,12 +125,6 @@ func Provider() *schema.Provider { }, }, }, - "is_serverless": { - Type: schema.TypeBool, - Description: "Flag if redshift is serverless.", - Optional: true, - Default: false, - }, }, ResourcesMap: map[string]*schema.Resource{ "redshift_user": redshiftUser(), @@ -166,7 +160,6 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { Database: d.Get("database").(string), SSLMode: d.Get("sslmode").(string), MaxConns: d.Get("max_connections").(int), - IsServerless: d.Get("is_serverless").(bool), } log.Println("[DEBUG] creating database client") diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index 6b868be..7244a08 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -450,7 +450,12 @@ func resourceRedshiftSchemaReadImpl(db *DBConnection, d *schema.ResourceData) er func resourceRedshiftSchemaReadLocal(db *DBConnection, d *schema.ResourceData) error { var schemaQuota int = 0 - if !db.client.config.IsServerless { + isServerless, err := db.client.config.IsServerless(db) + if err != nil { + return err + } + + if !isServerless { err := db.QueryRow(` SELECT COALESCE(quota, 0) @@ -463,7 +468,7 @@ func resourceRedshiftSchemaReadLocal(db *DBConnection, d *schema.ResourceData) e } d.Set(schemaQuotaAttr, schemaQuota) d.Set(schemaExternalSchemaAttr, nil) - + return nil } diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index a142c66..277716d 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -270,28 +270,16 @@ func resourceRedshiftUserRead(db *DBConnection, d *schema.ResourceData) error { } func resourceRedshiftUserReadImpl(db *DBConnection, d *schema.ResourceData) error { - var userName, userValidUntil, userConnLimit, userSyslogAccess, userInfoTable string + var userName, userValidUntil, userConnLimit, userSyslogAccess, userSessionTimeout string var userSuperuser, userCreateDB bool - var columns []string - - if db.client.config.IsServerless { - columns = []string{ - "usename", - "usecreatedb", - "usesuper", - "'RESTRICTED'", - "'UNLIMITED'", - } - userInfoTable = "pg_user" - } else { - columns = []string{ - "usename", - "usecreatedb", - "usesuper", - "syslogaccess", - `COALESCE(useconnlimit::TEXT, 'UNLIMITED')`, - } - userInfoTable = "svl_user_info" + + columns := []string{ + "user_name", + "createdb", + "superuser", + "syslog_access", + `COALESCE(connection_limit::TEXT, 'UNLIMITED')`, + "session_timeout", } values := []interface{}{ @@ -300,11 +288,12 @@ func resourceRedshiftUserReadImpl(db *DBConnection, d *schema.ResourceData) erro &userSuperuser, &userSyslogAccess, &userConnLimit, + &userSessionTimeout, } useSysID := d.Id() - userSQL := fmt.Sprintf("SELECT %s FROM %s WHERE usesysid = $1", strings.Join(columns, ","), userInfoTable) + userSQL := fmt.Sprintf("SELECT %s FROM svv_user_info WHERE user_id = $1", strings.Join(columns, ",")) err := db.QueryRow(userSQL, useSysID).Scan(values...) switch { case err == sql.ErrNoRows: