From 802b433069b35d1bce7c395a5c3ed5cff30cd11d Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 4 Aug 2025 19:12:18 +0200 Subject: [PATCH 01/34] wip: enable usage of aws redshift data api --- go.mod | 2 ++ go.sum | 12 ++++++++++++ redshift/config.go | 43 ++++++++++++++++--------------------------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index dc0ef2cb..89157879 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.20.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect @@ -65,6 +66,7 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect + github.com/mashiike/redshift-data-sql-driver v0.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect diff --git a/go.sum b/go.sum index 5f17c9eb..54726f85 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= github.com/aws/aws-sdk-go-v2 v1.37.0 h1:YtCOESR/pN4j5oA7cVHSfOwIcuh/KwHC4DOSXFbv5F0= github.com/aws/aws-sdk-go-v2 v1.37.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= github.com/aws/aws-sdk-go-v2/config v1.30.1 h1:sHL8g/+9tcZATeV2tEkEfxZeaNokDtKsSjGMGHD49qA= @@ -29,8 +30,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.18.1 h1:E55xvOqlX7CvB66Z7rSM9usCrFU1 github.com/aws/aws-sdk-go-v2/credentials v1.18.1/go.mod h1:iobSQfR5MkvILxssGOvi/P1jjOhrRzfTiCPCzku0vx4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 h1:9sBTeKQwAvmJUWKIACIoiFSnxxl+sS++YDfr17/ngq0= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0/go.mod h1:LW9/PxQD1SYFC7pnWcgqPhoyZprhjEdg5hBK6qYPLW8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 h1:H2iZoqW/v2Jnrh1FnU725Bq6KJ0k2uP63yH+DcY+HUI= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0/go.mod h1:L0FqLbwMXHvNC/7crWV1iIxUlOKYZUE8KuTIA+TozAI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 h1:EDped/rNzAhFPhVY0sDGbtD16OKqksfA8OjF/kLEgw8= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0/go.mod h1:uUI335jvzpZRPpjYx6ODc/wg1qH+NnoSTK/FwVeK0C0= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= @@ -41,12 +44,15 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 h1:eRhU3Sh8d github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0/go.mod h1:paNLV18DZ6FnWE/bd06RIKPDIFpjuvCkGKWTG/GDBeM= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0 h1:70T8EpAmUAmh1+iljlPu94NnUKATN9GedtKY0y9I4CY= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0/go.mod h1:ItDt61dKOBnzf5gY/kvu4UaDKNxdp8LntwS7PaaVpfU= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.20.5 h1:iIRfLBX36lMn7vXdaVF1PZV/jiBXeUpiL2KHkGOjVsc= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.20.5/go.mod h1:q++QEMyKK3FcyuHOuab73F3mtkmP/Xu25VkMSEgqpE0= github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 h1:cuFWHH87GP1NBGXXfMicUbE7Oty5KpPxN6w4JpmuxYc= github.com/aws/aws-sdk-go-v2/service/sso v1.26.0/go.mod h1:aJBemdlbCKyOXEXdXBqS7E+8S9XTDcOTaoOjtng54hA= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 h1:t2va+wewPOYIqC6XyJ4MGjiGKkczMAPsgq5W4FtL9ME= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0/go.mod h1:ExCTcqYqN0hYYRsDlBVU8+68grqlWdgX9/nZJwQW4aY= github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 h1:FD9agdG4CeOGS3ORLByJk56YIXDS7mxFpmZyCtpqExc= github.com/aws/aws-sdk-go-v2/service/sts v1.35.0/go.mod h1:NDzDPbBF1xtSTZUMuZx0w3hIfWzcL7X2AQ0Tr9becIQ= +github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= @@ -90,6 +96,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -154,6 +161,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -165,6 +174,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mashiike/redshift-data-sql-driver v0.2.0 h1:agU0/gcS0aiznmJwTbru14yDSKOi9dPU/wDGFean7hc= +github.com/mashiike/redshift-data-sql-driver v0.2.0/go.mod h1:VwxNm3Jlf4EM8r2lSPYCJ7MnNTSjiQzi83KXRXMzJPE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -318,6 +329,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/redshift/config.go b/redshift/config.go index 49e37e5b..88395c98 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -4,10 +4,12 @@ import ( "database/sql" "fmt" "net/url" + "os" "strings" "sync" _ "github.com/lib/pq" + _ "github.com/mashiike/redshift-data-sql-driver" ) var ( @@ -90,9 +92,20 @@ func (c *Client) Connect() (*DBConnection, error) { dsn := c.config.connStr(c.databaseName) conn, found := dbRegistry[dsn] if !found { - db, err := sql.Open(proxyDriverName, dsn) - if err != nil { - return nil, fmt.Errorf("error connecting to PostgreSQL server %q: %w", c.config.Host, err) + // todo: put values into config struct + workgroupName, ok := os.LookupEnv("SERVERLESS_WORKGROUP_NAME") + var db *sql.DB + var err error + if !ok { + db, err = sql.Open(proxyDriverName, dsn) + if err != nil { + return nil, fmt.Errorf("error connecting to Redshift server %q: %w", c.config.Host, err) + } + } else { + db, err = sql.Open("redshift-data", fmt.Sprintf("workgroup(%s)/%s?timeout=1m®ion=eu-central-1", workgroupName, c.config.Database)) + if err != nil { + return nil, fmt.Errorf("error connecting to redshift workgroup %q: %w", workgroupName, err) + } } // We don't want to retain connection @@ -139,30 +152,6 @@ func (c *Config) connParams() []string { return paramsArray } -// Client instantiates a new Redshift client. -func (c *Config) Client() (*Client, error) { - - conninfo := fmt.Sprintf("sslmode=%v user=%v password=%v host=%v port=%v dbname=%v", - c.SSLMode, - c.Username, - c.Password, - c.Host, - c.Port, - c.Database) - - db, err := sql.Open(proxyDriverName, conninfo) - if err != nil { - return nil, err - } - - client := Client{ - config: *c, - db: db, - } - - return &client, nil -} - func (c *Client) Close() { if c.db != nil { c.db.Close() From 5dc34c5f8975160445b274ad8db24f6c495de268 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 4 Aug 2025 19:13:17 +0200 Subject: [PATCH 02/34] do not query in transactions to enable usage of aws redshift data api --- redshift/helpers.go | 12 ++++---- redshift/resource_redshift_datashare.go | 30 ++++--------------- .../resource_redshift_default_privileges.go | 25 +++++----------- redshift/resource_redshift_group.go | 12 ++++---- redshift/resource_redshift_schema.go | 12 ++++---- redshift/resource_redshift_user.go | 6 ++-- redshift/resource_redshift_user_test.go | 14 +++++++-- 7 files changed, 46 insertions(+), 65 deletions(-) diff --git a/redshift/helpers.go b/redshift/helpers.go index c8c15c0b..f709e975 100644 --- a/redshift/helpers.go +++ b/redshift/helpers.go @@ -69,18 +69,18 @@ func pqQuoteLiteral(in string) string { return in } -func getGroupIDFromName(tx *sql.Tx, group string) (groupID int, err error) { - err = tx.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", group).Scan(&groupID) +func getGroupIDFromName(db *sql.DB, group string) (groupID int, err error) { + err = db.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", group).Scan(&groupID) return } -func getUserIDFromName(tx *sql.Tx, user string) (userID int, err error) { - err = tx.QueryRow("SELECT usesysid FROM pg_user WHERE usename = $1", user).Scan(&userID) +func getUserIDFromName(db *sql.DB, user string) (userID int, err error) { + err = db.QueryRow("SELECT usesysid FROM pg_user WHERE usename = $1", user).Scan(&userID) return } -func getSchemaIDFromName(tx *sql.Tx, schema string) (schemaID int, err error) { - err = tx.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", schema).Scan(&schemaID) +func getSchemaIDFromName(db *sql.DB, schema string) (schemaID int, err error) { + err = db.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", schema).Scan(&schemaID) return } diff --git a/redshift/resource_redshift_datashare.go b/redshift/resource_redshift_datashare.go index 2a053dcd..7951667e 100644 --- a/redshift/resource_redshift_datashare.go +++ b/redshift/resource_redshift_datashare.go @@ -114,7 +114,7 @@ func resourceRedshiftDatashareCreate(db *DBConnection, d *schema.ResourceData) e var shareId string query = "SELECT share_id FROM SVV_DATASHARES WHERE share_type = 'OUTBOUND' AND share_name = $1" log.Printf("[DEBUG] %s, $1=%s\n", query, strings.ToLower(shareName)) - if err := tx.QueryRow(query, strings.ToLower(shareName)).Scan(&shareId); err != nil { + if err := db.DB.QueryRow(query, strings.ToLower(shareName)).Scan(&shareId); err != nil { return err } @@ -261,7 +261,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err WHERE share_type = 'OUTBOUND' AND share_id = $1` log.Printf("[DEBUG] %s, $1=%s\n", query, d.Id()) - err = tx.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) + err = db.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) if err != nil { return err } @@ -273,7 +273,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err d.Set(dataShareProducerNamespaceAttr, producerNamespace) d.Set(dataShareCreatedAttr, created) - if err = readDatashareSchemas(tx, shareName, d); err != nil { + if err = readDatashareSchemas(db.DB, shareName, d); err != nil { return err } @@ -284,7 +284,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err return nil } -func readDatashareSchemas(tx *sql.Tx, shareName string, d *schema.ResourceData) error { +func readDatashareSchemas(db *sql.DB, shareName string, d *schema.ResourceData) error { query := ` SELECT object_name @@ -294,7 +294,7 @@ func readDatashareSchemas(tx *sql.Tx, shareName string, d *schema.ResourceData) AND share_name = $1 ` log.Printf("[DEBUG] %s, $1=%s\n", query, shareName) - rows, err := tx.Query(query, shareName) + rows, err := db.Query(query, shareName) if err != nil { return err } @@ -331,10 +331,6 @@ func resourceRedshiftDatashareUpdate(db *DBConnection, d *schema.ResourceData) e return err } - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftDatashareRead(db, d) } @@ -405,15 +401,9 @@ func setDatashareSchemas(tx *sql.Tx, d *schema.ResourceData) error { } func resourceRedshiftDatashareDelete(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - var shareName string query := "SELECT share_name FROM svv_datashares WHERE share_type='OUTBOUND' AND share_id=$1" - if err := tx.QueryRow(query, d.Id()).Scan(&shareName); err != nil { + if err := db.QueryRow(query, d.Id()).Scan(&shareName); err != nil { if errors.Is(err, sql.ErrNoRows) { log.Printf("[WARN] data share with id %s does not exist.\n", d.Id()) return nil @@ -422,13 +412,5 @@ func resourceRedshiftDatashareDelete(db *DBConnection, d *schema.ResourceData) e } query = fmt.Sprintf("DROP DATASHARE %s", pq.QuoteIdentifier(shareName)) log.Printf("[DEBUG] %s\n", query) - _, err = tx.Exec(query) - if err != nil { - return err - } - - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } return nil } diff --git a/redshift/resource_redshift_default_privileges.go b/redshift/resource_redshift_default_privileges.go index 86ac1f7a..9f7de8cc 100644 --- a/redshift/resource_redshift_default_privileges.go +++ b/redshift/resource_redshift_default_privileges.go @@ -162,16 +162,11 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou schemaName, schemaNameSet := d.GetOk(defaultPrivilegesSchemaAttr) ownerName := d.Get(defaultPrivilegesOwnerAttr).(string) - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - + var err error schemaID := defaultPrivilegesAllSchemasID if schemaNameSet { log.Printf("[DEBUG] getting ID for schema %s\n", schemaName) - schemaID, err = getSchemaIDFromName(tx, schemaName.(string)) + schemaID, err = getSchemaIDFromName(db.DB, schemaName.(string)) if err != nil { return fmt.Errorf("failed to get schema ID for schema '%s': %w", schemaName, err) } @@ -179,14 +174,14 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou if groupName, groupNameSet := d.GetOk(defaultPrivilegesGroupAttr); groupNameSet { log.Printf("[DEBUG] getting ID for group %s\n", groupName.(string)) - entityID, err = getGroupIDFromName(tx, groupName.(string)) + entityID, err = getGroupIDFromName(db.DB, groupName.(string)) entityIsUser = false if err != nil { return fmt.Errorf("failed to get group ID: %w", err) } } else if userName, userNameSet := d.GetOk(defaultPrivilegesUserAttr); userNameSet { log.Printf("[DEBUG] getting ID for user %s\n", userName.(string)) - entityID, err = getUserIDFromName(tx, userName.(string)) + entityID, err = getUserIDFromName(db.DB, userName.(string)) entityIsUser = true if err != nil { return fmt.Errorf("failed to get user ID: %w", err) @@ -194,7 +189,7 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou } log.Printf("[DEBUG] getting ID for owner %s\n", ownerName) - ownerID, err := getUserIDFromName(tx, ownerName) + ownerID, err := getUserIDFromName(db.DB, ownerName) if err != nil { return fmt.Errorf("failed to get user ID: %w", err) } @@ -202,19 +197,15 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou switch strings.ToUpper(d.Get(defaultPrivilegesObjectTypeAttr).(string)) { case "TABLE": log.Println("[DEBUG] reading default privileges") - if err := readGroupTableDefaultPrivileges(tx, d, entityID, schemaID, ownerID, entityIsUser); err != nil { + if err := readGroupTableDefaultPrivileges(db.DB, d, entityID, schemaID, ownerID, entityIsUser); err != nil { return fmt.Errorf("failed to read table privileges: %w", err) } } - if err := tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return nil } -func readGroupTableDefaultPrivileges(tx *sql.Tx, d *schema.ResourceData, entityID, schemaID, ownerID int, entityIsUser bool) error { +func readGroupTableDefaultPrivileges(db *sql.DB, d *schema.ResourceData, entityID, schemaID, ownerID int, entityIsUser bool) error { var tableSelect, tableUpdate, tableInsert, tableDelete, tableDrop, tableReferences, tableRule, tableTrigger bool var query string @@ -258,7 +249,7 @@ func readGroupTableDefaultPrivileges(tx *sql.Tx, d *schema.ResourceData, entityI ` } - if err := tx.QueryRow(query, schemaID, entityID, defaultPrivilegesObjectTypesCodes["table"], ownerID).Scan( + if err := db.QueryRow(query, schemaID, entityID, defaultPrivilegesObjectTypesCodes["table"], ownerID).Scan( &tableSelect, &tableUpdate, &tableInsert, diff --git a/redshift/resource_redshift_group.go b/redshift/resource_redshift_group.go index 9cf70df1..73c40945 100644 --- a/redshift/resource_redshift_group.go +++ b/redshift/resource_redshift_group.go @@ -101,7 +101,7 @@ func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error } var groSysID string - if err := tx.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", strings.ToLower(groupName)).Scan(&groSysID); err != nil { + if err := db.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", strings.ToLower(groupName)).Scan(&groSysID); err != nil { return fmt.Errorf("could not get redshift group id for %q: %w", groupName, err) } @@ -123,7 +123,7 @@ func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error } defer deferredRollback(tx) - rows, err := tx.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") + rows, err := db.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") if err != nil { return err } @@ -193,10 +193,10 @@ func setGroupName(tx *sql.Tx, d *schema.ResourceData) error { return nil } -func checkIfUserExists(tx *sql.Tx, name string) (bool, error) { +func checkIfUserExists(db *sql.DB, name string) (bool, error) { var result int - err := tx.QueryRow("SELECT 1 FROM pg_user_info WHERE usename=$1", name).Scan(&result) + err := db.QueryRow("SELECT 1 FROM pg_user_info WHERE usename=$1", name).Scan(&result) switch { case errors.Is(err, sql.ErrNoRows): @@ -208,7 +208,7 @@ func checkIfUserExists(tx *sql.Tx, name string) (bool, error) { return true, nil } -func setUsersNames(tx *sql.Tx, _ *DBConnection, d *schema.ResourceData) error { +func setUsersNames(tx *sql.Tx, db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(groupUsersAttr) { return nil } @@ -221,7 +221,7 @@ func setUsersNames(tx *sql.Tx, _ *DBConnection, d *schema.ResourceData) error { if removedUsers.Len() > 0 { var removedUsersNamesSafe []string for _, name := range removedUsers.List() { - userExists, err := checkIfUserExists(tx, name.(string)) + userExists, err := checkIfUserExists(db.DB, name.(string)) if err != nil { return err } diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index 1a2f239e..58159bd2 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -595,9 +595,9 @@ func resourceRedshiftSchemaCreate(db *DBConnection, d *schema.ResourceData) erro defer deferredRollback(tx) if _, isExternal := d.GetOk(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")); isExternal { - err = resourceRedshiftSchemaCreateExternal(tx, d) + err = resourceRedshiftSchemaCreateExternal(tx, db.DB, d) } else { - err = resourceRedshiftSchemaCreateInternal(tx, d) + err = resourceRedshiftSchemaCreateInternal(tx, db.DB, d) } if err != nil { return err @@ -610,7 +610,7 @@ func resourceRedshiftSchemaCreate(db *DBConnection, d *schema.ResourceData) erro return resourceRedshiftSchemaReadImpl(db, d) } -func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) schemaQuota := d.Get(schemaQuotaAttr).(int) var createOpts []string @@ -632,7 +632,7 @@ func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, d *schema.ResourceData) er } var schemaOID string - if err := tx.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { + if err := db.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { return err } @@ -641,7 +641,7 @@ func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, d *schema.ResourceData) er return nil } -func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) query := fmt.Sprintf("CREATE EXTERNAL SCHEMA %s", pq.QuoteIdentifier(schemaName)) sourceDbName := d.Get(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")).(string) @@ -681,7 +681,7 @@ func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, d *schema.ResourceData) er } var schemaOID string - if err := tx.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { + if err := db.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { return err } diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index 8312a093..e3061027 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -238,7 +238,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error } var usesysid string - if err := tx.QueryRow("SELECT usesysid FROM pg_user_info WHERE usename = $1", userName).Scan(&usesysid); err != nil { + if err := db.QueryRow("SELECT usesysid FROM pg_user_info WHERE usename = $1", userName).Scan(&usesysid); err != nil { return fmt.Errorf("user does not exist in pg_user_info table: %w", err) } @@ -371,7 +371,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error OWNER("userid", "ddl") WHERE owner.userid = $1;` - rows, err := tx.Query(reassignOwnerGenerator, useSysID, pq.QuoteIdentifier(newOwnerName)) + rows, err := db.Query(reassignOwnerGenerator, useSysID, pq.QuoteIdentifier(newOwnerName)) if err != nil { return err } @@ -394,7 +394,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error } } - rows, err = tx.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") + rows, err = db.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") if err != nil { return err } diff --git a/redshift/resource_redshift_user_test.go b/redshift/resource_redshift_user_test.go index 6883f540..b777ad18 100644 --- a/redshift/resource_redshift_user_test.go +++ b/redshift/resource_redshift_user_test.go @@ -487,7 +487,7 @@ func TestPermanentUsername(t *testing.T) { } func testAccCheckRedshiftUserCanLogin(user string, password string) resource.TestCheckFunc { - return func(s *terraform.State) error { + return func(s *terraform.State) (err error) { // there doesn't seem to be a good way to extract the provider configuration // at runtime. However we know we've configured the provider with default settings // so we can mimic the same behavior @@ -517,11 +517,19 @@ func testAccCheckRedshiftUserCanLogin(user string, password string) resource.Tes MaxConns: defaultProviderMaxOpenConnections, } - client, err := config.Client() + client := config.NewClient(database) + var conn *DBConnection + conn, err = client.Connect() if err != nil { return fmt.Errorf("user is unable to login: %w", err) } defer client.Close() - return nil + defer func(conn *DBConnection) { + closeErr := conn.Close() + if err == nil { + err = closeErr + } + }(conn) + return } } From cfdc938a1196a073a92535417a740a991a087e94 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 4 Aug 2025 20:32:57 +0200 Subject: [PATCH 03/34] use newer version of redshift data sql driver --- go.mod | 26 ++++++++++++++------------ go.sum | 56 ++++++++++++++++++++++++-------------------------------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 89157879..e47e26e3 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,15 @@ go 1.23.7 toolchain go1.24.5 require ( - github.com/aws/aws-sdk-go-v2 v1.37.0 - github.com/aws/aws-sdk-go-v2/config v1.30.1 - github.com/aws/aws-sdk-go-v2/credentials v1.18.1 + github.com/aws/aws-sdk-go-v2 v1.37.1 + github.com/aws/aws-sdk-go-v2/config v1.30.2 + github.com/aws/aws-sdk-go-v2/credentials v1.18.2 github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 github.com/hashicorp/terraform-plugin-docs v0.22.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 github.com/lib/pq v1.10.9 + github.com/mashiike/redshift-data-sql-driver v0.2.0 golang.org/x/net v0.42.0 ) @@ -26,15 +27,15 @@ require ( github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect - github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.20.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.34.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect @@ -66,7 +67,6 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect - github.com/mashiike/redshift-data-sql-driver v0.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect @@ -100,3 +100,5 @@ require ( gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/mashiike/redshift-data-sql-driver => github.com/mmichaelb/redshift-data-sql-driver v0.0.1 diff --git a/go.sum b/go.sum index 54726f85..35122c75 100644 --- a/go.sum +++ b/go.sum @@ -21,38 +21,34 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= -github.com/aws/aws-sdk-go-v2 v1.37.0 h1:YtCOESR/pN4j5oA7cVHSfOwIcuh/KwHC4DOSXFbv5F0= -github.com/aws/aws-sdk-go-v2 v1.37.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= -github.com/aws/aws-sdk-go-v2/config v1.30.1 h1:sHL8g/+9tcZATeV2tEkEfxZeaNokDtKsSjGMGHD49qA= -github.com/aws/aws-sdk-go-v2/config v1.30.1/go.mod h1:wkibEyFfxXRyTSzRU4bbF5IUsSXyE4xQ4ZjkGmi5tFo= -github.com/aws/aws-sdk-go-v2/credentials v1.18.1 h1:E55xvOqlX7CvB66Z7rSM9usCrFU1ryUIUHqiXsEzVoE= -github.com/aws/aws-sdk-go-v2/credentials v1.18.1/go.mod h1:iobSQfR5MkvILxssGOvi/P1jjOhrRzfTiCPCzku0vx4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 h1:9sBTeKQwAvmJUWKIACIoiFSnxxl+sS++YDfr17/ngq0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0/go.mod h1:LW9/PxQD1SYFC7pnWcgqPhoyZprhjEdg5hBK6qYPLW8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 h1:H2iZoqW/v2Jnrh1FnU725Bq6KJ0k2uP63yH+DcY+HUI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0/go.mod h1:L0FqLbwMXHvNC/7crWV1iIxUlOKYZUE8KuTIA+TozAI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 h1:EDped/rNzAhFPhVY0sDGbtD16OKqksfA8OjF/kLEgw8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0/go.mod h1:uUI335jvzpZRPpjYx6ODc/wg1qH+NnoSTK/FwVeK0C0= +github.com/aws/aws-sdk-go-v2 v1.37.1 h1:SMUxeNz3Z6nqGsXv0JuJXc8w5YMtrQMuIBmDx//bBDY= +github.com/aws/aws-sdk-go-v2 v1.37.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2/config v1.30.2 h1:YE1BmSc4fFYqFgN1mN8uzrtc7R9x+7oSWeX8ckoltAw= +github.com/aws/aws-sdk-go-v2/config v1.30.2/go.mod h1:UNrLGZ6jfAVjgVJpkIxjLufRJqTXCVYOpkeVf83kwBo= +github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs= +github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 h1:ksZXBYv80EFTcgc8OJO48aQ8XDWXIQL7gGasPeCoTzI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1/go.mod h1:HSksQyyJETVZS7uM54cir0IgxttTD+8aEoJMPGepHBI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 h1:+dn/xF/05utS7tUhjIcndbuaPjfll2LhbH1cCDGLYUQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1/go.mod h1:hyAGz30LHdm5KBZDI58MXx5lDVZ5CUfvfTZvMu4HCZo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 h1:eRhU3Sh8dGbaniI6B+I48XJMrTPRkK4DKo+vqIxziOU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0/go.mod h1:paNLV18DZ6FnWE/bd06RIKPDIFpjuvCkGKWTG/GDBeM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 h1:ky79ysLMxhwk5rxJtS+ILd3Mc8kC5fhsLBrP27r6h4I= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1/go.mod h1:+2MmkvFvPYM1vsozBWduoLJUi5maxFk5B7KJFECujhY= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0 h1:70T8EpAmUAmh1+iljlPu94NnUKATN9GedtKY0y9I4CY= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0/go.mod h1:ItDt61dKOBnzf5gY/kvu4UaDKNxdp8LntwS7PaaVpfU= -github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.20.5 h1:iIRfLBX36lMn7vXdaVF1PZV/jiBXeUpiL2KHkGOjVsc= -github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.20.5/go.mod h1:q++QEMyKK3FcyuHOuab73F3mtkmP/Xu25VkMSEgqpE0= -github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 h1:cuFWHH87GP1NBGXXfMicUbE7Oty5KpPxN6w4JpmuxYc= -github.com/aws/aws-sdk-go-v2/service/sso v1.26.0/go.mod h1:aJBemdlbCKyOXEXdXBqS7E+8S9XTDcOTaoOjtng54hA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 h1:t2va+wewPOYIqC6XyJ4MGjiGKkczMAPsgq5W4FtL9ME= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0/go.mod h1:ExCTcqYqN0hYYRsDlBVU8+68grqlWdgX9/nZJwQW4aY= -github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 h1:FD9agdG4CeOGS3ORLByJk56YIXDS7mxFpmZyCtpqExc= -github.com/aws/aws-sdk-go-v2/service/sts v1.35.0/go.mod h1:NDzDPbBF1xtSTZUMuZx0w3hIfWzcL7X2AQ0Tr9becIQ= -github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.34.1 h1:4+OFVGzIg6JcwbR8FKtYdC6AuSg1jV11Hk3RIv6y2oY= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.34.1/go.mod h1:heOxElSAAGnh+jfAH0hK9ilW856kSFYBlYRJS5QKbm0= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw= github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= @@ -96,7 +92,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -161,8 +156,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -174,8 +167,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mashiike/redshift-data-sql-driver v0.2.0 h1:agU0/gcS0aiznmJwTbru14yDSKOi9dPU/wDGFean7hc= -github.com/mashiike/redshift-data-sql-driver v0.2.0/go.mod h1:VwxNm3Jlf4EM8r2lSPYCJ7MnNTSjiQzi83KXRXMzJPE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -198,6 +189,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mmichaelb/redshift-data-sql-driver v0.0.1 h1:sl1dA7GgmwtMAg7ET3qyLc51JWuX7VaS8XKBLny8Gj4= +github.com/mmichaelb/redshift-data-sql-driver v0.0.1/go.mod h1:X1KwG24kLnavNul+xMDnOGEEQLd1UTgnydzXTrjkCZQ= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= @@ -329,7 +322,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 1b04183329741617bedea2d21bb9217f3a03e8bc Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Tue, 5 Aug 2025 11:11:10 +0200 Subject: [PATCH 04/34] fix invalid expected error message --- redshift/resource_redshift_user_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redshift/resource_redshift_user_test.go b/redshift/resource_redshift_user_test.go index b777ad18..3d0533af 100644 --- a/redshift/resource_redshift_user_test.go +++ b/redshift/resource_redshift_user_test.go @@ -230,7 +230,7 @@ resource "redshift_user" "superuser" { Steps: []resource.TestStep{ { Config: config, - ExpectError: regexp.MustCompile("Users that are superusers must define a password."), + ExpectError: regexp.MustCompile("users that are superusers must define a password"), }, }, }) From 507e56e0d20f81d41fb1e36a9651629cca6f4afb Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Tue, 5 Aug 2025 12:38:21 +0200 Subject: [PATCH 05/34] remove transactions to support aws redshift data api --- redshift/helpers.go | 34 ------- redshift/resource_redshift_database.go | 44 +++------ redshift/resource_redshift_datashare.go | 96 +++++++------------ .../resource_redshift_default_privileges.go | 25 +---- redshift/resource_redshift_grant.go | 35 ++----- redshift/resource_redshift_group.go | 50 +++------- redshift/resource_redshift_schema.go | 62 ++++-------- redshift/resource_redshift_user.go | 89 ++++++----------- 8 files changed, 117 insertions(+), 318 deletions(-) diff --git a/redshift/helpers.go b/redshift/helpers.go index f709e975..4034387f 100644 --- a/redshift/helpers.go +++ b/redshift/helpers.go @@ -6,7 +6,6 @@ import ( "encoding/csv" "errors" "fmt" - "log" "strings" "time" @@ -26,39 +25,6 @@ const ( pgErrorCodeInsufficientPrivileges = "42501" ) -// startTransaction starts a new DB transaction on the specified database. -// If the database is specified and different from the one configured in the provider, -// it will create a new connection pool if needed. -func startTransaction(client *Client, database string) (*sql.Tx, error) { - if database != "" && database != client.databaseName { - client = client.config.NewClient(database) - } - db, err := client.Connect() - if err != nil { - return nil, err - } - - txn, err := db.Begin() - if err != nil { - return nil, fmt.Errorf("could not start transaction: %w", err) - } - - return txn, nil -} - -// deferredRollback can be used to rollback a transaction in a defer. -// It will log an error if it fails -func deferredRollback(txn *sql.Tx) { - err := txn.Rollback() - switch { - case errors.Is(err, sql.ErrTxDone): - // transaction has already been committed or rolled back - log.Printf("[DEBUG]: %v", err) - case err != nil: - log.Printf("[ERR] could not rollback transaction: %v", err) - } -} - // pqQuoteLiteral returns a string literal safe for inclusion in a PostgreSQL // query as a parameter. The resulting string still needs to be wrapped in // single quotes in SQL (i.e. fmt.Sprintf(`'%s'`, pqQuoteLiteral("str"))). See diff --git a/redshift/resource_redshift_database.go b/redshift/resource_redshift_database.go index b40692c3..84585e44 100644 --- a/redshift/resource_redshift_database.go +++ b/redshift/resource_redshift_database.go @@ -1,7 +1,6 @@ package redshift import ( - "database/sql" "fmt" "log" "strconv" @@ -138,19 +137,11 @@ func resourceRedshiftDatabaseCreateFromDatashare(db *DBConnection, d *schema.Res } d.SetId(oid) - // CREATE DATABASE isn't allowed to run inside a transaction, however ALTER DATABASE - // can be - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - // CREATE DATABASE FROM DATASHARE... doesn't allow you to specify an owner in the create statement, // so we need to set the owner after creation using ALTER DATABASE... owner, ownerIsSet := d.GetOk(databaseOwnerAttr) if ownerIsSet { - if _, err = tx.Exec(fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner.(string)))); err != nil { + if _, err := db.Exec(fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner.(string)))); err != nil { return err } } @@ -159,13 +150,10 @@ func resourceRedshiftDatabaseCreateFromDatashare(db *DBConnection, d *schema.Res // so we need to set the owner after creation using ALTER DATABASE... connLimit, connLimitIsSet := d.GetOk(databaseConnLimitAttr) if connLimitIsSet { - if _, err = tx.Exec(fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(dbName), connLimit.(int))); err != nil { + if _, err := db.Exec(fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(dbName), connLimit.(int))); err != nil { return err } } - if err = tx.Commit(); err != nil { - return err - } return resourceRedshiftDatabaseRead(db, d) } @@ -251,32 +239,22 @@ WHERE pg_database_info.datid = $1 } func resourceRedshiftDatabaseUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { + if err := setDatabaseName(db, d); err != nil { return err } - defer deferredRollback(tx) - if err := setDatabaseName(tx, d); err != nil { + if err := setDatabaseOwner(db, d); err != nil { return err } - if err := setDatabaseOwner(tx, d); err != nil { + if err := setDatabaseConnLimit(db, d); err != nil { return err } - if err := setDatabaseConnLimit(tx, d); err != nil { - return err - } - - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftDatabaseRead(db, d) } -func setDatabaseName(tx *sql.Tx, d *schema.ResourceData) error { +func setDatabaseName(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(databaseNameAttr) { return nil } @@ -291,14 +269,14 @@ func setDatabaseName(tx *sql.Tx, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER DATABASE %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) log.Printf("[DEBUG] renaming database %s to %s: %s\n", oldValue, newValue, query) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating database NAME: %w", err) } return nil } -func setDatabaseOwner(tx *sql.Tx, d *schema.ResourceData) error { +func setDatabaseOwner(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(databaseOwnerAttr) { return nil } @@ -308,11 +286,11 @@ func setDatabaseOwner(tx *sql.Tx, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(databaseName), pq.QuoteIdentifier(databaseOwner)) log.Printf("[DEBUG] changing database owner: %s\n", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } -func setDatabaseConnLimit(tx *sql.Tx, d *schema.ResourceData) error { +func setDatabaseConnLimit(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(databaseConnLimitAttr) { return nil } @@ -321,7 +299,7 @@ func setDatabaseConnLimit(tx *sql.Tx, d *schema.ResourceData) error { connLimit := d.Get(databaseConnLimitAttr).(int) query := fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(databaseName), connLimit) log.Printf("[DEBUG] changing database connection limit: %s\n", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } diff --git a/redshift/resource_redshift_datashare.go b/redshift/resource_redshift_datashare.go index 7951667e..1b3cac02 100644 --- a/redshift/resource_redshift_datashare.go +++ b/redshift/resource_redshift_datashare.go @@ -97,17 +97,11 @@ such as RA3. } func resourceRedshiftDatashareCreate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - shareName := d.Get(dataShareNameAttr).(string) query := fmt.Sprintf("CREATE DATASHARE %s SET PUBLICACCESSIBLE = %t", pq.QuoteIdentifier(shareName), d.Get(dataSharePublicAccessibleAttr).(bool)) log.Printf("[DEBUG] %s\n", query) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return err } @@ -123,43 +117,39 @@ func resourceRedshiftDatashareCreate(db *DBConnection, d *schema.ResourceData) e if owner, ownerIsSet := d.GetOk(dataShareOwnerAttr); ownerIsSet { query = fmt.Sprintf("ALTER DATASHARE %s OWNER TO %s", pq.QuoteIdentifier(strings.ToLower(shareName)), pq.QuoteIdentifier(strings.ToLower(owner.(string)))) log.Printf("[DEBUG] %s\n", query) - _, err = tx.Exec(query) + _, err := db.Exec(query) if err != nil { return err } } for _, dataShareSchema := range d.Get(dataShareSchemasAttr).(*schema.Set).List() { - err = addSchemaToDatashare(tx, shareName, dataShareSchema.(string)) + err := addSchemaToDatashare(db, shareName, dataShareSchema.(string)) if err != nil { return err } } - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftDatashareRead(db, d) } -func addSchemaToDatashare(tx *sql.Tx, shareName string, schemaName string) error { - err := resourceRedshiftDatashareAddSchema(tx, shareName, schemaName) +func addSchemaToDatashare(db *DBConnection, shareName string, schemaName string) error { + err := resourceRedshiftDatashareAddSchema(db, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareAddAllTables(tx, shareName, schemaName) + err = resourceRedshiftDatashareAddAllTables(db, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareAddAllFunctions(tx, shareName, schemaName) + err = resourceRedshiftDatashareAddAllFunctions(db, shareName, schemaName) return err } -func resourceRedshiftDatashareAddSchema(tx *sql.Tx, shareName string, schemaName string) error { +func resourceRedshiftDatashareAddSchema(db *DBConnection, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s ADD SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) if err != nil { // if the schema is already in the datashare we get a "duplicate schema" error code. This is fine. var pqErr *pq.Error @@ -175,55 +165,55 @@ func resourceRedshiftDatashareAddSchema(tx *sql.Tx, shareName string, schemaName } query = fmt.Sprintf("ALTER DATASHARE %s SET INCLUDENEW = TRUE FOR SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err = tx.Exec(query) + _, err = db.Exec(query) return err } -func resourceRedshiftDatashareAddAllFunctions(tx *sql.Tx, shareName string, schemaName string) error { +func resourceRedshiftDatashareAddAllFunctions(db *DBConnection, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s ADD ALL FUNCTIONS IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } -func resourceRedshiftDatashareAddAllTables(tx *sql.Tx, shareName string, schemaName string) error { +func resourceRedshiftDatashareAddAllTables(db *DBConnection, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s ADD ALL TABLES IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } -func removeSchemaFromDatashare(tx *sql.Tx, shareName string, schemaName string) error { - err := resourceRedshiftDatashareRemoveAllFunctions(tx, shareName, schemaName) +func removeSchemaFromDatashare(db *DBConnection, shareName string, schemaName string) error { + err := resourceRedshiftDatashareRemoveAllFunctions(db, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareRemoveAllTables(tx, shareName, schemaName) + err = resourceRedshiftDatashareRemoveAllTables(db, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareRemoveSchema(tx, shareName, schemaName) + err = resourceRedshiftDatashareRemoveSchema(db, shareName, schemaName) return err } -func resourceRedshiftDatashareRemoveAllFunctions(tx *sql.Tx, shareName string, schemaName string) error { +func resourceRedshiftDatashareRemoveAllFunctions(db *DBConnection, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s REMOVE ALL FUNCTIONS IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } -func resourceRedshiftDatashareRemoveAllTables(tx *sql.Tx, shareName string, schemaName string) error { +func resourceRedshiftDatashareRemoveAllTables(db *DBConnection, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s REMOVE ALL TABLES IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } -func resourceRedshiftDatashareRemoveSchema(tx *sql.Tx, shareName string, schemaName string) error { +func resourceRedshiftDatashareRemoveSchema(db *DBConnection, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s REMOVE SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := tx.Exec(query) + _, err := db.Exec(query) if err != nil { // if the schema is not already in the datashare we get a "datashare does not contain schema" error code. This is fine. var pqErr *pq.Error @@ -242,12 +232,6 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err var shareName, owner, producerAccount, producerNamespace, created string var publicAccessible bool - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - query := ` SELECT TRIM(svv_datashares.share_name), @@ -261,7 +245,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err WHERE share_type = 'OUTBOUND' AND share_id = $1` log.Printf("[DEBUG] %s, $1=%s\n", query, d.Id()) - err = db.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) + err := db.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) if err != nil { return err } @@ -277,10 +261,6 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err return err } - if err = tx.Commit(); err != nil { - return err - } - return nil } @@ -313,28 +293,22 @@ func readDatashareSchemas(db *sql.DB, shareName string, d *schema.ResourceData) } func resourceRedshiftDatashareUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - - if err := setDatashareOwner(tx, d); err != nil { + if err := setDatashareOwner(db, d); err != nil { return err } - if err := setDatasharePubliclyAccessble(tx, d); err != nil { + if err := setDatasharePubliclyAccessble(db, d); err != nil { return err } - if err := setDatashareSchemas(tx, d); err != nil { + if err := setDatashareSchemas(db, d); err != nil { return err } return resourceRedshiftDatashareRead(db, d) } -func setDatashareOwner(tx *sql.Tx, d *schema.ResourceData) error { +func setDatashareOwner(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(dataShareOwnerAttr) { return nil } @@ -349,13 +323,13 @@ func setDatashareOwner(tx *sql.Tx, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER DATASHARE %s OWNER TO %s", pq.QuoteIdentifier(shareName), newValue) log.Printf("[DEBUG] %s\n", query) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating datashare OWNER: %w", err) } return nil } -func setDatasharePubliclyAccessble(tx *sql.Tx, d *schema.ResourceData) error { +func setDatasharePubliclyAccessble(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(dataSharePublicAccessibleAttr) { return nil } @@ -364,13 +338,13 @@ func setDatasharePubliclyAccessble(tx *sql.Tx, d *schema.ResourceData) error { newValue := d.Get(dataSharePublicAccessibleAttr).(bool) query := fmt.Sprintf("ALTER DATASHARE %s SET PUBLICACCESSIBLE %t", pq.QuoteIdentifier(shareName), newValue) log.Printf("[DEBUG] %s\n", query) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating datashare PUBLICACCESSBILE: %w", err) } return nil } -func setDatashareSchemas(tx *sql.Tx, d *schema.ResourceData) error { +func setDatashareSchemas(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(dataShareSchemasAttr) { return nil } @@ -387,12 +361,12 @@ func setDatashareSchemas(tx *sql.Tx, d *schema.ResourceData) error { shareName := d.Get(dataShareNameAttr).(string) for _, s := range add.List() { - if err := addSchemaToDatashare(tx, shareName, s.(string)); err != nil { + if err := addSchemaToDatashare(db, shareName, s.(string)); err != nil { return err } } for _, s := range remove.List() { - if err := removeSchemaFromDatashare(tx, shareName, s.(string)); err != nil { + if err := removeSchemaFromDatashare(db, shareName, s.(string)); err != nil { return err } } diff --git a/redshift/resource_redshift_default_privileges.go b/redshift/resource_redshift_default_privileges.go index 9f7de8cc..34dee7ab 100644 --- a/redshift/resource_redshift_default_privileges.go +++ b/redshift/resource_redshift_default_privileges.go @@ -99,17 +99,10 @@ func redshiftDefaultPrivileges() *schema.Resource { func resourceRedshiftDefaultPrivilegesDelete(db *DBConnection, d *schema.ResourceData) error { revokeAlterDefaultQuery := createAlterDefaultsRevokeQuery(d) - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - - if _, err := tx.Exec(revokeAlterDefaultQuery); err != nil { + if _, err := db.Exec(revokeAlterDefaultQuery); err != nil { return err } - - return tx.Commit() + return nil } func resourceRedshiftDefaultPrivilegesCreate(db *DBConnection, d *schema.ResourceData) error { @@ -125,28 +118,18 @@ func resourceRedshiftDefaultPrivilegesCreate(db *DBConnection, d *schema.Resourc return fmt.Errorf(`invalid privileges list %+v for object type %q`, privileges, objectType) } - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - revokeAlterDefaultQuery := createAlterDefaultsRevokeQuery(d) - if _, err := tx.Exec(revokeAlterDefaultQuery); err != nil { + if _, err := db.Exec(revokeAlterDefaultQuery); err != nil { return err } if len(privileges) > 0 { alterDefaultQuery := createAlterDefaultsGrantQuery(d, privileges) - if _, err := tx.Exec(alterDefaultQuery); err != nil { + if _, err := db.Exec(alterDefaultQuery); err != nil { return err } } - if err := tx.Commit(); err != nil { - return err - } - d.SetId(generateDefaultPrivilegesID(d)) return resourceRedshiftDefaultPrivilegesReadImpl(db, d) diff --git a/redshift/resource_redshift_grant.go b/redshift/resource_redshift_grant.go index f254d4c4..effce379 100644 --- a/redshift/resource_redshift_grant.go +++ b/redshift/resource_redshift_grant.go @@ -1,7 +1,6 @@ package redshift import ( - "database/sql" "fmt" "log" "regexp" @@ -156,46 +155,26 @@ func resourceRedshiftGrantCreate(db *DBConnection, d *schema.ResourceData) error databaseName := getDatabaseName(db, d) - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - - if err := revokeGrants(tx, databaseName, d); err != nil { + if err := revokeGrants(db, databaseName, d); err != nil { return err } - if err := createGrants(tx, databaseName, d); err != nil { + if err := createGrants(db, databaseName, d); err != nil { return err } - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - d.SetId(generateGrantID(d)) return resourceRedshiftGrantReadImpl(db, d) } func resourceRedshiftGrantDelete(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - databaseName := getDatabaseName(db, d) - if err := revokeGrants(tx, databaseName, d); err != nil { + if err := revokeGrants(db, databaseName, d); err != nil { return err } - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return nil } @@ -657,20 +636,20 @@ func readLanguageGrants(db *DBConnection, d *schema.ResourceData) error { return nil } -func revokeGrants(tx *sql.Tx, databaseName string, d *schema.ResourceData) error { +func revokeGrants(db *DBConnection, databaseName string, d *schema.ResourceData) error { query := createGrantsRevokeQuery(d, databaseName) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } -func createGrants(tx *sql.Tx, databaseName string, d *schema.ResourceData) error { +func createGrants(db *DBConnection, databaseName string, d *schema.ResourceData) error { if d.Get(grantPrivilegesAttr).(*schema.Set).Len() == 0 { log.Printf("[DEBUG] no privileges to grant for %s", d.Get(grantGroupAttr).(string)) return nil } query := createGrantsQuery(d, databaseName) - _, err := tx.Exec(query) + _, err := db.Exec(query) return err } diff --git a/redshift/resource_redshift_group.go b/redshift/resource_redshift_group.go index 73c40945..594a39bc 100644 --- a/redshift/resource_redshift_group.go +++ b/redshift/resource_redshift_group.go @@ -78,12 +78,6 @@ func resourceRedshiftGroupReadImpl(db *DBConnection, d *schema.ResourceData) err func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error { groupName := d.Get(groupNameAttr).(string) - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - query := fmt.Sprintf("CREATE GROUP %s", pq.QuoteIdentifier(groupName)) if v, ok := d.GetOk(groupUsersAttr); ok && len(v.(*schema.Set).List()) > 0 { usernames := v.(*schema.Set).List() @@ -96,7 +90,7 @@ func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error query = fmt.Sprintf("%s WITH USER %s", query, strings.Join(usernamesSafe, ", ")) } - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("could not create redshift group: %w", err) } @@ -107,22 +101,12 @@ func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error d.SetId(groSysID) - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftGroupReadImpl(db, d) } func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error { groupName := d.Get(groupNameAttr).(string) - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - rows, err := db.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") if err != nil { return err @@ -135,44 +119,34 @@ func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error return err } - if _, err := tx.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { + if _, err := db.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { return err } - if _, err := tx.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { + if _, err := db.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { return err } } - if _, err := tx.Exec(fmt.Sprintf("DROP GROUP %s", pq.QuoteIdentifier(groupName))); err != nil { + if _, err := db.Exec(fmt.Sprintf("DROP GROUP %s", pq.QuoteIdentifier(groupName))); err != nil { return err } - return tx.Commit() + return nil } func resourceRedshiftGroupUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - - if err := setGroupName(tx, d); err != nil { + if err := setGroupName(db, d); err != nil { return err } - if err := setUsersNames(tx, db, d); err != nil { + if err := setUsersNames(db, d); err != nil { return err } - if err := tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftGroupReadImpl(db, d) } -func setGroupName(tx *sql.Tx, d *schema.ResourceData) error { +func setGroupName(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(groupNameAttr) { return nil } @@ -186,7 +160,7 @@ func setGroupName(tx *sql.Tx, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER GROUP %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating Group NAME: %w", err) } @@ -208,7 +182,7 @@ func checkIfUserExists(db *sql.DB, name string) (bool, error) { return true, nil } -func setUsersNames(tx *sql.Tx, db *DBConnection, d *schema.ResourceData) error { +func setUsersNames(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(groupUsersAttr) { return nil } @@ -234,7 +208,7 @@ func setUsersNames(tx *sql.Tx, db *DBConnection, d *schema.ResourceData) error { if len(removedUsersNamesSafe) > 0 { query := fmt.Sprintf("ALTER GROUP %s DROP USER %s", pq.QuoteIdentifier(groupName), strings.Join(removedUsersNamesSafe, ", ")) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return err } } @@ -248,7 +222,7 @@ func setUsersNames(tx *sql.Tx, db *DBConnection, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER GROUP %s ADD USER %s", pq.QuoteIdentifier(groupName), strings.Join(addedUsersNamesSafe, ", ")) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return err } } diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index 58159bd2..422d452b 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -567,11 +567,6 @@ func resourceRedshiftSchemaReadExternal(db *DBConnection, d *schema.ResourceData } func resourceRedshiftSchemaDelete(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) schemaName := d.Get(schemaNameAttr).(string) cascadeOrRestrict := "RESTRICT" @@ -580,37 +575,28 @@ func resourceRedshiftSchemaDelete(db *DBConnection, d *schema.ResourceData) erro } query := fmt.Sprintf("DROP SCHEMA %s %s", pq.QuoteIdentifier(schemaName), cascadeOrRestrict) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return err } - return tx.Commit() + return nil } func resourceRedshiftSchemaCreate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - + var err error if _, isExternal := d.GetOk(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")); isExternal { - err = resourceRedshiftSchemaCreateExternal(tx, db.DB, d) + err = resourceRedshiftSchemaCreateExternal(db, d) } else { - err = resourceRedshiftSchemaCreateInternal(tx, db.DB, d) + err = resourceRedshiftSchemaCreateInternal(db, d) } if err != nil { return err } - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftSchemaReadImpl(db, d) } -func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateInternal(db *DBConnection, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) schemaQuota := d.Get(schemaQuotaAttr).(int) var createOpts []string @@ -627,7 +613,7 @@ func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.Reso query := fmt.Sprintf("CREATE SCHEMA %s %s", pq.QuoteIdentifier(schemaName), strings.Join(createOpts, " ")) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return err } @@ -641,7 +627,7 @@ func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.Reso return nil } -func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateExternal(db *DBConnection, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) query := fmt.Sprintf("CREATE EXTERNAL SCHEMA %s", pq.QuoteIdentifier(schemaName)) sourceDbName := d.Get(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")).(string) @@ -668,14 +654,14 @@ func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, db *sql.DB, d *schema.Reso query = fmt.Sprintf("%s %s", query, configQuery) log.Printf("[DEBUG] creating external schema: %s\n", query) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return err } if v, ok := d.GetOk(schemaOwnerAttr); ok { query = fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(v.(string))) log.Printf("[DEBUG] setting schema owner: %s\n", query) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return err } } @@ -780,32 +766,22 @@ func getRedshiftConfigQueryPart(d *schema.ResourceData, sourceDbName string) str } func resourceRedshiftSchemaUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - - if err := setSchemaName(tx, d); err != nil { + if err := setSchemaName(db, d); err != nil { return err } - if err := setSchemaOwner(tx, db, d); err != nil { + if err := setSchemaOwner(db, db, d); err != nil { return err } - if err := setSchemaQuota(tx, d); err != nil { + if err := setSchemaQuota(db, d); err != nil { return err } - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftSchemaReadImpl(db, d) } -func setSchemaName(tx *sql.Tx, d *schema.ResourceData) error { +func setSchemaName(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(schemaNameAttr) { return nil } @@ -819,14 +795,14 @@ func setSchemaName(tx *sql.Tx, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER SCHEMA %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating schema NAME: %w", err) } return nil } -func setSchemaOwner(tx *sql.Tx, _ *DBConnection, d *schema.ResourceData) error { +func setSchemaOwner(db *DBConnection, _ *DBConnection, d *schema.ResourceData) error { if !d.HasChange(schemaOwnerAttr) { return nil } @@ -834,11 +810,11 @@ func setSchemaOwner(tx *sql.Tx, _ *DBConnection, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) schemaOwner := d.Get(schemaOwnerAttr).(string) - _, err := tx.Exec(fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(schemaOwner))) + _, err := db.Exec(fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(schemaOwner))) return err } -func setSchemaQuota(tx *sql.Tx, d *schema.ResourceData) error { +func setSchemaQuota(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(schemaQuotaAttr) { return nil } @@ -851,6 +827,6 @@ func setSchemaQuota(tx *sql.Tx, d *schema.ResourceData) error { quotaValue = fmt.Sprintf("%d GB", schemaQuota) } - _, err := tx.Exec(fmt.Sprintf("ALTER SCHEMA %s QUOTA %s", pq.QuoteIdentifier(schemaName), quotaValue)) + _, err := db.Exec(fmt.Sprintf("ALTER SCHEMA %s QUOTA %s", pq.QuoteIdentifier(schemaName), quotaValue)) return err } diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index e3061027..53aee40f 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -140,12 +140,6 @@ Amazon Redshift user accounts can only be created and dropped by a database supe } func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - stringOpts := []struct { hclKey string sqlKey string @@ -233,7 +227,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error createStr := strings.Join(createOpts, " ") query := fmt.Sprintf("CREATE USER %s WITH %s", pq.QuoteIdentifier(userName), createStr) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error creating user %s: %w", userName, err) } @@ -244,10 +238,6 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error d.SetId(usesysid) - if err = tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftUserReadImpl(db, d) } @@ -327,12 +317,6 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error userName := d.Get(userNameAttr).(string) newOwnerName := permanentUsername(db.client.config.Username) - tx, err := startTransaction(db.client, "") - if err != nil { - return err - } - defer deferredRollback(tx) - // Based on https://github.com/awslabs/amazon-redshift-utils/blob/master/src/AdminViews/v_find_dropuser_objs.sql var reassignOwnerGenerator = `SELECT owner.ddl FROM ( @@ -388,7 +372,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error } for _, statement := range reassignStatements { - if _, err := tx.Exec(statement); err != nil { + if _, err := db.Exec(statement); err != nil { log.Printf("error: %#v", err) return err } @@ -406,74 +390,59 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error return err } - if _, err := tx.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { + if _, err := db.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { return err } - if _, err := tx.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { + if _, err := db.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { return err } } - if _, err := tx.Exec(fmt.Sprintf("DROP USER %s", pq.QuoteIdentifier(userName))); err != nil { + if _, err := db.Exec(fmt.Sprintf("DROP USER %s", pq.QuoteIdentifier(userName))); err != nil { return err } - if err := tx.Commit(); err != nil { - return err - //return fmt.Errorf("could not commit transaction: %w", err) - } - return nil } func resourceRedshiftUserUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") - if err != nil { + if err := setUserName(db, d); err != nil { return err } - defer deferredRollback(tx) - if err := setUserName(tx, d); err != nil { + if err := setUserPassword(db, d); err != nil { return err } - if err := setUserPassword(tx, d); err != nil { + if err := setUserConnLimit(db, d); err != nil { return err } - if err := setUserConnLimit(tx, d); err != nil { + if err := setUserCreateDB(db, d); err != nil { return err } - - if err := setUserCreateDB(tx, d); err != nil { - return err - } - if err := setUserSuperuser(tx, d); err != nil { + if err := setUserSuperuser(db, d); err != nil { return err } - if err := setUserValidUntil(tx, d); err != nil { + if err := setUserValidUntil(db, d); err != nil { return err } - if err := setUserSyslogAccess(tx, d); err != nil { + if err := setUserSyslogAccess(db, d); err != nil { return err } - if err := setUserSessionTimeout(tx, d); err != nil { + if err := setUserSessionTimeout(db, d); err != nil { return err } - if err := tx.Commit(); err != nil { - return fmt.Errorf("could not commit transaction: %w", err) - } - return resourceRedshiftUserReadImpl(db, d) } -func setUserName(tx *sql.Tx, d *schema.ResourceData) error { +func setUserName(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(userNameAttr) { return nil } @@ -487,14 +456,14 @@ func setUserName(tx *sql.Tx, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER USER %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating User NAME: %w", err) } return nil } -func setUserPassword(tx *sql.Tx, d *schema.ResourceData) error { +func setUserPassword(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(userPasswordAttr) && !d.HasChange(userNameAttr) { return nil } @@ -508,13 +477,13 @@ func setUserPassword(tx *sql.Tx, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER USER %s %s", pq.QuoteIdentifier(userName), passwdTok) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating user password: %w", err) } return nil } -func setUserConnLimit(tx *sql.Tx, d *schema.ResourceData) error { +func setUserConnLimit(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(userConnLimitAttr) { return nil } @@ -522,14 +491,14 @@ func setUserConnLimit(tx *sql.Tx, d *schema.ResourceData) error { connLimit := d.Get(userConnLimitAttr).(int) userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s CONNECTION LIMIT %d", pq.QuoteIdentifier(userName), connLimit) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating user CONNECTION LIMIT: %w", err) } return nil } -func setUserSessionTimeout(tx *sql.Tx, d *schema.ResourceData) error { +func setUserSessionTimeout(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(userSessionTimeoutAttr) { return nil } @@ -542,14 +511,14 @@ func setUserSessionTimeout(tx *sql.Tx, d *schema.ResourceData) error { } else { query = fmt.Sprintf("ALTER USER %s SESSION TIMEOUT %d", pq.QuoteIdentifier(userName), sessionTimeout) } - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating user SESSION TIMEOUT: %w", err) } return nil } -func setUserCreateDB(tx *sql.Tx, d *schema.ResourceData) error { +func setUserCreateDB(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(userCreateDBAttr) { return nil } @@ -561,14 +530,14 @@ func setUserCreateDB(tx *sql.Tx, d *schema.ResourceData) error { } userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s WITH %s", pq.QuoteIdentifier(userName), tok) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating user CREATEDB: %w", err) } return nil } -func setUserSuperuser(tx *sql.Tx, d *schema.ResourceData) error { +func setUserSuperuser(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(userSuperuserAttr) { return nil } @@ -580,14 +549,14 @@ func setUserSuperuser(tx *sql.Tx, d *schema.ResourceData) error { } userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s WITH %s", pq.QuoteIdentifier(userName), tok) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating user SUPERUSER: %w", err) } return nil } -func setUserValidUntil(tx *sql.Tx, d *schema.ResourceData) error { +func setUserValidUntil(db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(userValidUntilAttr) { return nil } @@ -601,14 +570,14 @@ func setUserValidUntil(tx *sql.Tx, d *schema.ResourceData) error { userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s VALID UNTIL '%s'", pq.QuoteIdentifier(userName), pqQuoteLiteral(validUntil)) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating user VALID UNTIL: %w", err) } return nil } -func setUserSyslogAccess(tx *sql.Tx, d *schema.ResourceData) error { +func setUserSyslogAccess(db *DBConnection, d *schema.ResourceData) error { syslogAccessCurrent := d.Get(userSyslogAccessAttr).(string) syslogAccessComputed := syslogAccessCurrent if syslogAccessComputed == "" { @@ -625,7 +594,7 @@ func setUserSyslogAccess(tx *sql.Tx, d *schema.ResourceData) error { userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s WITH SYSLOG ACCESS %s", pq.QuoteIdentifier(userName), syslogAccessComputed) - if _, err := tx.Exec(query); err != nil { + if _, err := db.Exec(query); err != nil { return fmt.Errorf("error updating user SYSLOG ACCESS: %w", err) } From fc24851d42b19397abee0357f27b758a56708ed6 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Tue, 5 Aug 2025 13:40:56 +0200 Subject: [PATCH 06/34] add possibility to declare root username dynamically --- ...source_redshift_default_privileges_test.go | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/redshift/resource_redshift_default_privileges_test.go b/redshift/resource_redshift_default_privileges_test.go index 805f2e8a..8ee1001b 100644 --- a/redshift/resource_redshift_default_privileges_test.go +++ b/redshift/resource_redshift_default_privileges_test.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" "fmt" + "os" "regexp" "strings" "testing" @@ -22,6 +23,7 @@ func TestAccRedshiftDefaultPrivileges_Basic(t *testing.T) { strings.ReplaceAll(acctest.RandomWithPrefix("tf_acc_user"), "-", "_"), strings.ReplaceAll(acctest.RandomWithPrefix("tf_acc_user@tf_acc_domain.tld"), "-", "_"), } + rootUsername := getRootUsername() for i, groupName := range groupNames { userName := userNames[i] @@ -37,18 +39,18 @@ func TestAccRedshiftDefaultPrivileges_Basic(t *testing.T) { resource "redshift_default_privileges" "group" { group = redshift_group.group.name - owner = "root" + owner = %[3]q object_type = "table" privileges = ["select", "update", "insert", "delete", "drop", "references", "rule", "trigger"] } resource "redshift_default_privileges" "user" { user = redshift_user.user.name - owner = "root" + owner = %[3]q object_type = "table" privileges = ["select", "update", "insert", "delete", "drop", "references", "rule", "trigger"] } - `, groupName, userName) + `, groupName, userName, rootUsername) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviders, @@ -98,6 +100,7 @@ func TestAccRedshiftDefaultPrivileges_UpdateToRevoke(t *testing.T) { strings.ReplaceAll(acctest.RandomWithPrefix("tf_acc_user"), "-", "_"), strings.ReplaceAll(acctest.RandomWithPrefix("tf_acc_user@tf_acc_domain.tld"), "-", "_"), } + rootUsername := getRootUsername() for i, groupName := range groupNames { userName := userNames[i] @@ -113,18 +116,18 @@ func TestAccRedshiftDefaultPrivileges_UpdateToRevoke(t *testing.T) { resource "redshift_default_privileges" "group" { group = redshift_group.group.name - owner = "root" + owner = %[3]q object_type = "table" privileges = ["select", "update", "insert", "delete", "drop", "references", "rule", "trigger"] } resource "redshift_default_privileges" "user" { user = redshift_user.user.name - owner = "root" + owner = %[3]q object_type = "table" privileges = ["select", "update", "insert", "delete", "drop", "references", "rule", "trigger"] } - `, groupName, userName) + `, groupName, userName, rootUsername) configUpdated := fmt.Sprintf(` resource "redshift_group" "group" { @@ -138,18 +141,18 @@ func TestAccRedshiftDefaultPrivileges_UpdateToRevoke(t *testing.T) { resource "redshift_default_privileges" "group" { group = redshift_group.group.name - owner = "root" + owner = %[3]q object_type = "table" privileges = [] } resource "redshift_default_privileges" "user" { user = redshift_user.user.name - owner = "root" + owner = %[3]q object_type = "table" privileges = [] } - `, groupName, userName) + `, groupName, userName, rootUsername) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviders, @@ -205,16 +208,17 @@ func TestAccRedshiftDefaultPrivileges_UpdateToRevoke(t *testing.T) { } func TestAccRedshiftDefaultPrivileges_BothUserGroupError(t *testing.T) { - config := ` + rootUsername := getRootUsername() + config := fmt.Sprintf(` resource "redshift_default_privileges" "both" { user = "test_user" group = "test_group" - owner = "root" + owner = %[1]q object_type = "table" privileges = [] } -` +`, rootUsername) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviders, @@ -228,13 +232,14 @@ resource "redshift_default_privileges" "both" { } func TestAccRedshiftDefaultPrivileges_NoUserGroupError(t *testing.T) { - config := ` + rootUsername := getRootUsername() + config := fmt.Sprintf(` resource "redshift_default_privileges" "none" { - owner = "root" + owner = %[1]q object_type = "table" privileges = [] } -` +`, rootUsername) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviders, @@ -294,3 +299,11 @@ func checkDefACLExists(client *Client, schemaID, ownerID int, objectType, groupN return true, nil } + +func getRootUsername() string { + envRootUsername := os.Getenv("REDSHIFT_ROOT_USERNAME") + if envRootUsername == "" { + return "root" + } + return envRootUsername +} From 482d494aea8fc091d1f873f808ab9dcb7a1b48bc Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Tue, 5 Aug 2025 16:56:28 +0200 Subject: [PATCH 07/34] update redshift data sql driver to fix query escaping --- go.mod | 2 +- go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e47e26e3..96ef5e52 100644 --- a/go.mod +++ b/go.mod @@ -101,4 +101,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/mashiike/redshift-data-sql-driver => github.com/mmichaelb/redshift-data-sql-driver v0.0.1 +replace github.com/mashiike/redshift-data-sql-driver => github.com/mmichaelb/redshift-data-sql-driver v0.0.2 diff --git a/go.sum b/go.sum index 35122c75..eded3e2b 100644 --- a/go.sum +++ b/go.sum @@ -191,6 +191,7 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mmichaelb/redshift-data-sql-driver v0.0.1 h1:sl1dA7GgmwtMAg7ET3qyLc51JWuX7VaS8XKBLny8Gj4= github.com/mmichaelb/redshift-data-sql-driver v0.0.1/go.mod h1:X1KwG24kLnavNul+xMDnOGEEQLd1UTgnydzXTrjkCZQ= +github.com/mmichaelb/redshift-data-sql-driver v0.0.2/go.mod h1:X1KwG24kLnavNul+xMDnOGEEQLd1UTgnydzXTrjkCZQ= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= From 56842151c13180b58ed2107ef7dda9fe11b6cd09 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Tue, 5 Aug 2025 17:08:59 +0200 Subject: [PATCH 08/34] update go mod lockfile --- go.sum | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go.sum b/go.sum index eded3e2b..020d030b 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,7 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mmichaelb/redshift-data-sql-driver v0.0.1 h1:sl1dA7GgmwtMAg7ET3qyLc51JWuX7VaS8XKBLny8Gj4= -github.com/mmichaelb/redshift-data-sql-driver v0.0.1/go.mod h1:X1KwG24kLnavNul+xMDnOGEEQLd1UTgnydzXTrjkCZQ= +github.com/mmichaelb/redshift-data-sql-driver v0.0.2 h1:KDTnCBYFCUxnge5auEdP03PbhIHe2djC9izblW6YBOE= github.com/mmichaelb/redshift-data-sql-driver v0.0.2/go.mod h1:X1KwG24kLnavNul+xMDnOGEEQLd1UTgnydzXTrjkCZQ= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= From 3237a816c08d7c6115580847ce4c0a578aadbe5e Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Tue, 5 Aug 2025 17:23:07 +0200 Subject: [PATCH 09/34] apply dynamic root user name for test assertion --- .../resource_redshift_default_privileges_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/redshift/resource_redshift_default_privileges_test.go b/redshift/resource_redshift_default_privileges_test.go index 8ee1001b..e5b73910 100644 --- a/redshift/resource_redshift_default_privileges_test.go +++ b/redshift/resource_redshift_default_privileges_test.go @@ -59,7 +59,7 @@ func TestAccRedshiftDefaultPrivileges_Basic(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("redshift_default_privileges.group", "id", fmt.Sprintf("gn:%s_noschema_on:root_ot:table", groupName)), + resource.TestCheckResourceAttr("redshift_default_privileges.group", "id", fmt.Sprintf("gn:%s_noschema_on:%s_ot:table", groupName, rootUsername)), resource.TestCheckResourceAttr("redshift_default_privileges.group", "group", groupName), resource.TestCheckResourceAttr("redshift_default_privileges.group", "object_type", "table"), resource.TestCheckResourceAttr("redshift_default_privileges.group", "privileges.#", "8"), @@ -72,7 +72,7 @@ func TestAccRedshiftDefaultPrivileges_Basic(t *testing.T) { resource.TestCheckTypeSetElemAttr("redshift_default_privileges.group", "privileges.*", "rule"), resource.TestCheckTypeSetElemAttr("redshift_default_privileges.group", "privileges.*", "trigger"), - resource.TestCheckResourceAttr("redshift_default_privileges.user", "id", fmt.Sprintf("un:%s_noschema_on:root_ot:table", userName)), + resource.TestCheckResourceAttr("redshift_default_privileges.user", "id", fmt.Sprintf("un:%s_noschema_on:%s_ot:table", userName, rootUsername)), resource.TestCheckResourceAttr("redshift_default_privileges.user", "user", userName), resource.TestCheckResourceAttr("redshift_default_privileges.user", "object_type", "table"), resource.TestCheckResourceAttr("redshift_default_privileges.user", "privileges.#", "8"), @@ -161,7 +161,7 @@ func TestAccRedshiftDefaultPrivileges_UpdateToRevoke(t *testing.T) { { Config: configInitial, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("redshift_default_privileges.group", "id", fmt.Sprintf("gn:%s_noschema_on:root_ot:table", groupName)), + resource.TestCheckResourceAttr("redshift_default_privileges.group", "id", fmt.Sprintf("gn:%s_noschema_on:%s_ot:table", groupName, rootUsername)), resource.TestCheckResourceAttr("redshift_default_privileges.group", "group", groupName), resource.TestCheckResourceAttr("redshift_default_privileges.group", "object_type", "table"), resource.TestCheckResourceAttr("redshift_default_privileges.group", "privileges.#", "8"), @@ -174,7 +174,7 @@ func TestAccRedshiftDefaultPrivileges_UpdateToRevoke(t *testing.T) { resource.TestCheckTypeSetElemAttr("redshift_default_privileges.group", "privileges.*", "rule"), resource.TestCheckTypeSetElemAttr("redshift_default_privileges.group", "privileges.*", "trigger"), - resource.TestCheckResourceAttr("redshift_default_privileges.user", "id", fmt.Sprintf("un:%s_noschema_on:root_ot:table", userName)), + resource.TestCheckResourceAttr("redshift_default_privileges.user", "id", fmt.Sprintf("un:%s_noschema_on:%s_ot:table", userName, rootUsername)), resource.TestCheckResourceAttr("redshift_default_privileges.user", "user", userName), resource.TestCheckResourceAttr("redshift_default_privileges.user", "object_type", "table"), resource.TestCheckResourceAttr("redshift_default_privileges.user", "privileges.#", "8"), @@ -191,12 +191,12 @@ func TestAccRedshiftDefaultPrivileges_UpdateToRevoke(t *testing.T) { { Config: configUpdated, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("redshift_default_privileges.group", "id", fmt.Sprintf("gn:%s_noschema_on:root_ot:table", groupName)), + resource.TestCheckResourceAttr("redshift_default_privileges.group", "id", fmt.Sprintf("gn:%s_noschema_on:%s_ot:table", groupName, rootUsername)), resource.TestCheckResourceAttr("redshift_default_privileges.group", "group", groupName), resource.TestCheckResourceAttr("redshift_default_privileges.group", "object_type", "table"), resource.TestCheckResourceAttr("redshift_default_privileges.group", "privileges.#", "0"), - resource.TestCheckResourceAttr("redshift_default_privileges.user", "id", fmt.Sprintf("un:%s_noschema_on:root_ot:table", userName)), + resource.TestCheckResourceAttr("redshift_default_privileges.user", "id", fmt.Sprintf("un:%s_noschema_on:%s_ot:table", userName, rootUsername)), resource.TestCheckResourceAttr("redshift_default_privileges.user", "user", userName), resource.TestCheckResourceAttr("redshift_default_privileges.user", "object_type", "table"), resource.TestCheckResourceAttr("redshift_default_privileges.user", "privileges.#", "0"), From 5fc92eabcee56cfd73b3cd075cb8afb8a6fd9aa3 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 14 Aug 2025 11:35:18 +0200 Subject: [PATCH 10/34] use forked version of redshift data sql provider --- go.mod | 26 +++++++++++------------ go.sum | 52 +++++++++++++++++++++++----------------------- redshift/config.go | 2 +- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index 96ef5e52..9d02a636 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,15 @@ go 1.23.7 toolchain go1.24.5 require ( - github.com/aws/aws-sdk-go-v2 v1.37.1 - github.com/aws/aws-sdk-go-v2/config v1.30.2 - github.com/aws/aws-sdk-go-v2/credentials v1.18.2 + github.com/aws/aws-sdk-go-v2 v1.37.2 + github.com/aws/aws-sdk-go-v2/config v1.30.3 + github.com/aws/aws-sdk-go-v2/credentials v1.18.3 github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.36.0 github.com/hashicorp/terraform-plugin-docs v0.22.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 github.com/lib/pq v1.10.9 - github.com/mashiike/redshift-data-sql-driver v0.2.0 + github.com/mmichaelb/redshift-data-sql-driver v0.0.3 golang.org/x/net v0.42.0 ) @@ -27,15 +27,15 @@ require ( github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.34.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.2 // indirect + github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.35.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.27.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.32.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect @@ -100,5 +100,3 @@ require ( gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/mashiike/redshift-data-sql-driver => github.com/mmichaelb/redshift-data-sql-driver v0.0.2 diff --git a/go.sum b/go.sum index 020d030b..f2075940 100644 --- a/go.sum +++ b/go.sum @@ -21,34 +21,34 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.37.1 h1:SMUxeNz3Z6nqGsXv0JuJXc8w5YMtrQMuIBmDx//bBDY= -github.com/aws/aws-sdk-go-v2 v1.37.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= -github.com/aws/aws-sdk-go-v2/config v1.30.2 h1:YE1BmSc4fFYqFgN1mN8uzrtc7R9x+7oSWeX8ckoltAw= -github.com/aws/aws-sdk-go-v2/config v1.30.2/go.mod h1:UNrLGZ6jfAVjgVJpkIxjLufRJqTXCVYOpkeVf83kwBo= -github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs= -github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 h1:ksZXBYv80EFTcgc8OJO48aQ8XDWXIQL7gGasPeCoTzI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1/go.mod h1:HSksQyyJETVZS7uM54cir0IgxttTD+8aEoJMPGepHBI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 h1:+dn/xF/05utS7tUhjIcndbuaPjfll2LhbH1cCDGLYUQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1/go.mod h1:hyAGz30LHdm5KBZDI58MXx5lDVZ5CUfvfTZvMu4HCZo= +github.com/aws/aws-sdk-go-v2 v1.37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo= +github.com/aws/aws-sdk-go-v2 v1.37.2/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2/config v1.30.3 h1:utupeVnE3bmB221W08P0Moz1lDI3OwYa2fBtUhl7TCc= +github.com/aws/aws-sdk-go-v2/config v1.30.3/go.mod h1:NDGwOEBdpyZwLPlQkpKIO7frf18BW8PaCmAM9iUxQmI= +github.com/aws/aws-sdk-go-v2/credentials v1.18.3 h1:ptfyXmv+ooxzFwyuBth0yqABcjVIkjDL0iTYZBSbum8= +github.com/aws/aws-sdk-go-v2/credentials v1.18.3/go.mod h1:Q43Nci++Wohb0qUh4m54sNln0dbxJw8PvQWkrwOkGOI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 h1:nRniHAvjFJGUCl04F3WaAj7qp/rcz5Gi1OVoj5ErBkc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2/go.mod h1:eJDFKAMHHUvv4a0Zfa7bQb//wFNUXGrbFpYRCHe2kD0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 h1:sPiRHLVUIIQcoVZTNwqQcdtjkqkPopyYmIX0M5ElRf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2/go.mod h1:ik86P3sgV+Bk7c1tBFCwI3VxMoSEwl4YkRB9xn1s340= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 h1:ZdzDAg075H6stMZtbD2o+PyB933M/f20e9WmCBC17wA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2/go.mod h1:eE1IIzXG9sdZCB0pNNpMpsYTLl4YdOQD3njiVN1e/E4= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 h1:ky79ysLMxhwk5rxJtS+ILd3Mc8kC5fhsLBrP27r6h4I= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1/go.mod h1:+2MmkvFvPYM1vsozBWduoLJUi5maxFk5B7KJFECujhY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.2 h1:oxmDEO14NBZJbK/M8y3brhMFEIGN4j8a6Aq8eY0sqlo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.2/go.mod h1:4hH+8QCrk1uRWDPsVfsNDUup3taAjO8Dnx63au7smAU= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0 h1:70T8EpAmUAmh1+iljlPu94NnUKATN9GedtKY0y9I4CY= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0/go.mod h1:ItDt61dKOBnzf5gY/kvu4UaDKNxdp8LntwS7PaaVpfU= -github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.34.1 h1:4+OFVGzIg6JcwbR8FKtYdC6AuSg1jV11Hk3RIv6y2oY= -github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.34.1/go.mod h1:heOxElSAAGnh+jfAH0hK9ilW856kSFYBlYRJS5QKbm0= -github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY= -github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I= -github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.35.0 h1:2uGuWyvd7uCOEcEMN+SWkJsXlXOPrzfBtElITMnVAp4= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.35.0/go.mod h1:6Xy8SN1liN5+zoTMZ1C2UpeUdJURWSqr9u8wkOtSpdE= +github.com/aws/aws-sdk-go-v2/service/sso v1.27.0 h1:j7/jTOjWeJDolPwZ/J4yZ7dUsxsWZEsxNwH5O7F8eEA= +github.com/aws/aws-sdk-go-v2/service/sso v1.27.0/go.mod h1:M0xdEPQtgpNT7kdAX4/vOAPkFj60hSQRb7TvW9B0iug= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.32.0 h1:ywQF2N4VjqX+Psw+jLjMmUL2g1RDHlvri3NxHA08MGI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.32.0/go.mod h1:Z+qv5Q6b7sWiclvbJyPSOT1BRVU9wfSUPaqQzZ1Xg3E= +github.com/aws/aws-sdk-go-v2/service/sts v1.36.0 h1:bRP/a9llXSSgDPk7Rqn5GD/DQCGo6uk95plBFKoXt2M= +github.com/aws/aws-sdk-go-v2/service/sts v1.36.0/go.mod h1:tgBsFzxwl65BWkuJ/x2EUs59bD4SfYKgikvFDJi1S58= github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= @@ -189,8 +189,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mmichaelb/redshift-data-sql-driver v0.0.2 h1:KDTnCBYFCUxnge5auEdP03PbhIHe2djC9izblW6YBOE= -github.com/mmichaelb/redshift-data-sql-driver v0.0.2/go.mod h1:X1KwG24kLnavNul+xMDnOGEEQLd1UTgnydzXTrjkCZQ= +github.com/mmichaelb/redshift-data-sql-driver v0.0.3 h1:xOEYPczRHpbwduJSfjk/EqKCiSkw2nk1AciHsRFzYLk= +github.com/mmichaelb/redshift-data-sql-driver v0.0.3/go.mod h1:0g7EsDHiEl9MQd7D6eOJOlruhuV6ie/9XxprwdOpchU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= @@ -216,8 +216,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= diff --git a/redshift/config.go b/redshift/config.go index 88395c98..5f8503a1 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -9,7 +9,7 @@ import ( "sync" _ "github.com/lib/pq" - _ "github.com/mashiike/redshift-data-sql-driver" + _ "github.com/mmichaelb/redshift-data-sql-driver" ) var ( From 8e770fcab254fceeceb64b4932759381e4ee4914 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 14 Aug 2025 11:35:48 +0200 Subject: [PATCH 11/34] disable transactions in redshift data provider and set request mode to blocking --- redshift/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redshift/config.go b/redshift/config.go index 5f8503a1..44ec5a66 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -102,7 +102,7 @@ func (c *Client) Connect() (*DBConnection, error) { return nil, fmt.Errorf("error connecting to Redshift server %q: %w", c.config.Host, err) } } else { - db, err = sql.Open("redshift-data", fmt.Sprintf("workgroup(%s)/%s?timeout=1m®ion=eu-central-1", workgroupName, c.config.Database)) + db, err = sql.Open("redshift-data", fmt.Sprintf("workgroup(%s)/%s?timeout=1m®ion=eu-central-1&transactionMode=non-transactional&requestMode=blocking", workgroupName, c.config.Database)) if err != nil { return nil, fmt.Errorf("error connecting to redshift workgroup %q: %w", workgroupName, err) } From 2cd8b70be9687641a0cd60c451e1207a8bf67bad Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 14 Aug 2025 11:38:15 +0200 Subject: [PATCH 12/34] Revert "remove transactions to support aws redshift data api" This reverts commit 507e56e0d20f81d41fb1e36a9651629cca6f4afb. --- redshift/helpers.go | 34 +++++++ redshift/resource_redshift_database.go | 43 ++++++--- redshift/resource_redshift_datashare.go | 96 ++++++++++++------- .../resource_redshift_default_privileges.go | 25 ++++- redshift/resource_redshift_grant.go | 35 +++++-- redshift/resource_redshift_group.go | 50 +++++++--- redshift/resource_redshift_schema.go | 62 ++++++++---- redshift/resource_redshift_user.go | 89 +++++++++++------ 8 files changed, 317 insertions(+), 117 deletions(-) diff --git a/redshift/helpers.go b/redshift/helpers.go index 4034387f..f709e975 100644 --- a/redshift/helpers.go +++ b/redshift/helpers.go @@ -6,6 +6,7 @@ import ( "encoding/csv" "errors" "fmt" + "log" "strings" "time" @@ -25,6 +26,39 @@ const ( pgErrorCodeInsufficientPrivileges = "42501" ) +// startTransaction starts a new DB transaction on the specified database. +// If the database is specified and different from the one configured in the provider, +// it will create a new connection pool if needed. +func startTransaction(client *Client, database string) (*sql.Tx, error) { + if database != "" && database != client.databaseName { + client = client.config.NewClient(database) + } + db, err := client.Connect() + if err != nil { + return nil, err + } + + txn, err := db.Begin() + if err != nil { + return nil, fmt.Errorf("could not start transaction: %w", err) + } + + return txn, nil +} + +// deferredRollback can be used to rollback a transaction in a defer. +// It will log an error if it fails +func deferredRollback(txn *sql.Tx) { + err := txn.Rollback() + switch { + case errors.Is(err, sql.ErrTxDone): + // transaction has already been committed or rolled back + log.Printf("[DEBUG]: %v", err) + case err != nil: + log.Printf("[ERR] could not rollback transaction: %v", err) + } +} + // pqQuoteLiteral returns a string literal safe for inclusion in a PostgreSQL // query as a parameter. The resulting string still needs to be wrapped in // single quotes in SQL (i.e. fmt.Sprintf(`'%s'`, pqQuoteLiteral("str"))). See diff --git a/redshift/resource_redshift_database.go b/redshift/resource_redshift_database.go index 84585e44..565442f4 100644 --- a/redshift/resource_redshift_database.go +++ b/redshift/resource_redshift_database.go @@ -137,11 +137,19 @@ func resourceRedshiftDatabaseCreateFromDatashare(db *DBConnection, d *schema.Res } d.SetId(oid) + // CREATE DATABASE isn't allowed to run inside a transaction, however ALTER DATABASE + // can be + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + // CREATE DATABASE FROM DATASHARE... doesn't allow you to specify an owner in the create statement, // so we need to set the owner after creation using ALTER DATABASE... owner, ownerIsSet := d.GetOk(databaseOwnerAttr) if ownerIsSet { - if _, err := db.Exec(fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner.(string)))); err != nil { + if _, err = tx.Exec(fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner.(string)))); err != nil { return err } } @@ -150,10 +158,13 @@ func resourceRedshiftDatabaseCreateFromDatashare(db *DBConnection, d *schema.Res // so we need to set the owner after creation using ALTER DATABASE... connLimit, connLimitIsSet := d.GetOk(databaseConnLimitAttr) if connLimitIsSet { - if _, err := db.Exec(fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(dbName), connLimit.(int))); err != nil { + if _, err = tx.Exec(fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(dbName), connLimit.(int))); err != nil { return err } } + if err = tx.Commit(); err != nil { + return err + } return resourceRedshiftDatabaseRead(db, d) } @@ -239,22 +250,32 @@ WHERE pg_database_info.datid = $1 } func resourceRedshiftDatabaseUpdate(db *DBConnection, d *schema.ResourceData) error { - if err := setDatabaseName(db, d); err != nil { + tx, err := startTransaction(db.client, "") + if err != nil { return err } + defer deferredRollback(tx) - if err := setDatabaseOwner(db, d); err != nil { + if err := setDatabaseName(tx, d); err != nil { return err } - if err := setDatabaseConnLimit(db, d); err != nil { + if err := setDatabaseOwner(tx, d); err != nil { return err } + if err := setDatabaseConnLimit(tx, d); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftDatabaseRead(db, d) } -func setDatabaseName(db *DBConnection, d *schema.ResourceData) error { +func setDatabaseName(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(databaseNameAttr) { return nil } @@ -269,14 +290,14 @@ func setDatabaseName(db *DBConnection, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER DATABASE %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) log.Printf("[DEBUG] renaming database %s to %s: %s\n", oldValue, newValue, query) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating database NAME: %w", err) } return nil } -func setDatabaseOwner(db *DBConnection, d *schema.ResourceData) error { +func setDatabaseOwner(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(databaseOwnerAttr) { return nil } @@ -286,11 +307,11 @@ func setDatabaseOwner(db *DBConnection, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(databaseName), pq.QuoteIdentifier(databaseOwner)) log.Printf("[DEBUG] changing database owner: %s\n", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } -func setDatabaseConnLimit(db *DBConnection, d *schema.ResourceData) error { +func setDatabaseConnLimit(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(databaseConnLimitAttr) { return nil } @@ -299,7 +320,7 @@ func setDatabaseConnLimit(db *DBConnection, d *schema.ResourceData) error { connLimit := d.Get(databaseConnLimitAttr).(int) query := fmt.Sprintf("ALTER DATABASE %s CONNECTION LIMIT %d", pq.QuoteIdentifier(databaseName), connLimit) log.Printf("[DEBUG] changing database connection limit: %s\n", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } diff --git a/redshift/resource_redshift_datashare.go b/redshift/resource_redshift_datashare.go index 1b3cac02..7951667e 100644 --- a/redshift/resource_redshift_datashare.go +++ b/redshift/resource_redshift_datashare.go @@ -97,11 +97,17 @@ such as RA3. } func resourceRedshiftDatashareCreate(db *DBConnection, d *schema.ResourceData) error { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + shareName := d.Get(dataShareNameAttr).(string) query := fmt.Sprintf("CREATE DATASHARE %s SET PUBLICACCESSIBLE = %t", pq.QuoteIdentifier(shareName), d.Get(dataSharePublicAccessibleAttr).(bool)) log.Printf("[DEBUG] %s\n", query) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return err } @@ -117,39 +123,43 @@ func resourceRedshiftDatashareCreate(db *DBConnection, d *schema.ResourceData) e if owner, ownerIsSet := d.GetOk(dataShareOwnerAttr); ownerIsSet { query = fmt.Sprintf("ALTER DATASHARE %s OWNER TO %s", pq.QuoteIdentifier(strings.ToLower(shareName)), pq.QuoteIdentifier(strings.ToLower(owner.(string)))) log.Printf("[DEBUG] %s\n", query) - _, err := db.Exec(query) + _, err = tx.Exec(query) if err != nil { return err } } for _, dataShareSchema := range d.Get(dataShareSchemasAttr).(*schema.Set).List() { - err := addSchemaToDatashare(db, shareName, dataShareSchema.(string)) + err = addSchemaToDatashare(tx, shareName, dataShareSchema.(string)) if err != nil { return err } } + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftDatashareRead(db, d) } -func addSchemaToDatashare(db *DBConnection, shareName string, schemaName string) error { - err := resourceRedshiftDatashareAddSchema(db, shareName, schemaName) +func addSchemaToDatashare(tx *sql.Tx, shareName string, schemaName string) error { + err := resourceRedshiftDatashareAddSchema(tx, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareAddAllTables(db, shareName, schemaName) + err = resourceRedshiftDatashareAddAllTables(tx, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareAddAllFunctions(db, shareName, schemaName) + err = resourceRedshiftDatashareAddAllFunctions(tx, shareName, schemaName) return err } -func resourceRedshiftDatashareAddSchema(db *DBConnection, shareName string, schemaName string) error { +func resourceRedshiftDatashareAddSchema(tx *sql.Tx, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s ADD SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) if err != nil { // if the schema is already in the datashare we get a "duplicate schema" error code. This is fine. var pqErr *pq.Error @@ -165,55 +175,55 @@ func resourceRedshiftDatashareAddSchema(db *DBConnection, shareName string, sche } query = fmt.Sprintf("ALTER DATASHARE %s SET INCLUDENEW = TRUE FOR SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err = db.Exec(query) + _, err = tx.Exec(query) return err } -func resourceRedshiftDatashareAddAllFunctions(db *DBConnection, shareName string, schemaName string) error { +func resourceRedshiftDatashareAddAllFunctions(tx *sql.Tx, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s ADD ALL FUNCTIONS IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } -func resourceRedshiftDatashareAddAllTables(db *DBConnection, shareName string, schemaName string) error { +func resourceRedshiftDatashareAddAllTables(tx *sql.Tx, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s ADD ALL TABLES IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } -func removeSchemaFromDatashare(db *DBConnection, shareName string, schemaName string) error { - err := resourceRedshiftDatashareRemoveAllFunctions(db, shareName, schemaName) +func removeSchemaFromDatashare(tx *sql.Tx, shareName string, schemaName string) error { + err := resourceRedshiftDatashareRemoveAllFunctions(tx, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareRemoveAllTables(db, shareName, schemaName) + err = resourceRedshiftDatashareRemoveAllTables(tx, shareName, schemaName) if err != nil { return err } - err = resourceRedshiftDatashareRemoveSchema(db, shareName, schemaName) + err = resourceRedshiftDatashareRemoveSchema(tx, shareName, schemaName) return err } -func resourceRedshiftDatashareRemoveAllFunctions(db *DBConnection, shareName string, schemaName string) error { +func resourceRedshiftDatashareRemoveAllFunctions(tx *sql.Tx, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s REMOVE ALL FUNCTIONS IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } -func resourceRedshiftDatashareRemoveAllTables(db *DBConnection, shareName string, schemaName string) error { +func resourceRedshiftDatashareRemoveAllTables(tx *sql.Tx, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s REMOVE ALL TABLES IN SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } -func resourceRedshiftDatashareRemoveSchema(db *DBConnection, shareName string, schemaName string) error { +func resourceRedshiftDatashareRemoveSchema(tx *sql.Tx, shareName string, schemaName string) error { query := fmt.Sprintf("ALTER DATASHARE %s REMOVE SCHEMA %s", pq.QuoteIdentifier(shareName), pq.QuoteIdentifier(schemaName)) log.Printf("[DEBUG] %s\n", query) - _, err := db.Exec(query) + _, err := tx.Exec(query) if err != nil { // if the schema is not already in the datashare we get a "datashare does not contain schema" error code. This is fine. var pqErr *pq.Error @@ -232,6 +242,12 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err var shareName, owner, producerAccount, producerNamespace, created string var publicAccessible bool + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + query := ` SELECT TRIM(svv_datashares.share_name), @@ -245,7 +261,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err WHERE share_type = 'OUTBOUND' AND share_id = $1` log.Printf("[DEBUG] %s, $1=%s\n", query, d.Id()) - err := db.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) + err = db.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) if err != nil { return err } @@ -261,6 +277,10 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err return err } + if err = tx.Commit(); err != nil { + return err + } + return nil } @@ -293,22 +313,28 @@ func readDatashareSchemas(db *sql.DB, shareName string, d *schema.ResourceData) } func resourceRedshiftDatashareUpdate(db *DBConnection, d *schema.ResourceData) error { - if err := setDatashareOwner(db, d); err != nil { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + + if err := setDatashareOwner(tx, d); err != nil { return err } - if err := setDatasharePubliclyAccessble(db, d); err != nil { + if err := setDatasharePubliclyAccessble(tx, d); err != nil { return err } - if err := setDatashareSchemas(db, d); err != nil { + if err := setDatashareSchemas(tx, d); err != nil { return err } return resourceRedshiftDatashareRead(db, d) } -func setDatashareOwner(db *DBConnection, d *schema.ResourceData) error { +func setDatashareOwner(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(dataShareOwnerAttr) { return nil } @@ -323,13 +349,13 @@ func setDatashareOwner(db *DBConnection, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER DATASHARE %s OWNER TO %s", pq.QuoteIdentifier(shareName), newValue) log.Printf("[DEBUG] %s\n", query) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating datashare OWNER: %w", err) } return nil } -func setDatasharePubliclyAccessble(db *DBConnection, d *schema.ResourceData) error { +func setDatasharePubliclyAccessble(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(dataSharePublicAccessibleAttr) { return nil } @@ -338,13 +364,13 @@ func setDatasharePubliclyAccessble(db *DBConnection, d *schema.ResourceData) err newValue := d.Get(dataSharePublicAccessibleAttr).(bool) query := fmt.Sprintf("ALTER DATASHARE %s SET PUBLICACCESSIBLE %t", pq.QuoteIdentifier(shareName), newValue) log.Printf("[DEBUG] %s\n", query) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating datashare PUBLICACCESSBILE: %w", err) } return nil } -func setDatashareSchemas(db *DBConnection, d *schema.ResourceData) error { +func setDatashareSchemas(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(dataShareSchemasAttr) { return nil } @@ -361,12 +387,12 @@ func setDatashareSchemas(db *DBConnection, d *schema.ResourceData) error { shareName := d.Get(dataShareNameAttr).(string) for _, s := range add.List() { - if err := addSchemaToDatashare(db, shareName, s.(string)); err != nil { + if err := addSchemaToDatashare(tx, shareName, s.(string)); err != nil { return err } } for _, s := range remove.List() { - if err := removeSchemaFromDatashare(db, shareName, s.(string)); err != nil { + if err := removeSchemaFromDatashare(tx, shareName, s.(string)); err != nil { return err } } diff --git a/redshift/resource_redshift_default_privileges.go b/redshift/resource_redshift_default_privileges.go index 34dee7ab..9f7de8cc 100644 --- a/redshift/resource_redshift_default_privileges.go +++ b/redshift/resource_redshift_default_privileges.go @@ -99,10 +99,17 @@ func redshiftDefaultPrivileges() *schema.Resource { func resourceRedshiftDefaultPrivilegesDelete(db *DBConnection, d *schema.ResourceData) error { revokeAlterDefaultQuery := createAlterDefaultsRevokeQuery(d) - if _, err := db.Exec(revokeAlterDefaultQuery); err != nil { + tx, err := startTransaction(db.client, "") + if err != nil { return err } - return nil + defer deferredRollback(tx) + + if _, err := tx.Exec(revokeAlterDefaultQuery); err != nil { + return err + } + + return tx.Commit() } func resourceRedshiftDefaultPrivilegesCreate(db *DBConnection, d *schema.ResourceData) error { @@ -118,18 +125,28 @@ func resourceRedshiftDefaultPrivilegesCreate(db *DBConnection, d *schema.Resourc return fmt.Errorf(`invalid privileges list %+v for object type %q`, privileges, objectType) } + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + revokeAlterDefaultQuery := createAlterDefaultsRevokeQuery(d) - if _, err := db.Exec(revokeAlterDefaultQuery); err != nil { + if _, err := tx.Exec(revokeAlterDefaultQuery); err != nil { return err } if len(privileges) > 0 { alterDefaultQuery := createAlterDefaultsGrantQuery(d, privileges) - if _, err := db.Exec(alterDefaultQuery); err != nil { + if _, err := tx.Exec(alterDefaultQuery); err != nil { return err } } + if err := tx.Commit(); err != nil { + return err + } + d.SetId(generateDefaultPrivilegesID(d)) return resourceRedshiftDefaultPrivilegesReadImpl(db, d) diff --git a/redshift/resource_redshift_grant.go b/redshift/resource_redshift_grant.go index effce379..f254d4c4 100644 --- a/redshift/resource_redshift_grant.go +++ b/redshift/resource_redshift_grant.go @@ -1,6 +1,7 @@ package redshift import ( + "database/sql" "fmt" "log" "regexp" @@ -155,26 +156,46 @@ func resourceRedshiftGrantCreate(db *DBConnection, d *schema.ResourceData) error databaseName := getDatabaseName(db, d) - if err := revokeGrants(db, databaseName, d); err != nil { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + + if err := revokeGrants(tx, databaseName, d); err != nil { return err } - if err := createGrants(db, databaseName, d); err != nil { + if err := createGrants(tx, databaseName, d); err != nil { return err } + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + d.SetId(generateGrantID(d)) return resourceRedshiftGrantReadImpl(db, d) } func resourceRedshiftGrantDelete(db *DBConnection, d *schema.ResourceData) error { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + databaseName := getDatabaseName(db, d) - if err := revokeGrants(db, databaseName, d); err != nil { + if err := revokeGrants(tx, databaseName, d); err != nil { return err } + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return nil } @@ -636,20 +657,20 @@ func readLanguageGrants(db *DBConnection, d *schema.ResourceData) error { return nil } -func revokeGrants(db *DBConnection, databaseName string, d *schema.ResourceData) error { +func revokeGrants(tx *sql.Tx, databaseName string, d *schema.ResourceData) error { query := createGrantsRevokeQuery(d, databaseName) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } -func createGrants(db *DBConnection, databaseName string, d *schema.ResourceData) error { +func createGrants(tx *sql.Tx, databaseName string, d *schema.ResourceData) error { if d.Get(grantPrivilegesAttr).(*schema.Set).Len() == 0 { log.Printf("[DEBUG] no privileges to grant for %s", d.Get(grantGroupAttr).(string)) return nil } query := createGrantsQuery(d, databaseName) - _, err := db.Exec(query) + _, err := tx.Exec(query) return err } diff --git a/redshift/resource_redshift_group.go b/redshift/resource_redshift_group.go index 594a39bc..73c40945 100644 --- a/redshift/resource_redshift_group.go +++ b/redshift/resource_redshift_group.go @@ -78,6 +78,12 @@ func resourceRedshiftGroupReadImpl(db *DBConnection, d *schema.ResourceData) err func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error { groupName := d.Get(groupNameAttr).(string) + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + query := fmt.Sprintf("CREATE GROUP %s", pq.QuoteIdentifier(groupName)) if v, ok := d.GetOk(groupUsersAttr); ok && len(v.(*schema.Set).List()) > 0 { usernames := v.(*schema.Set).List() @@ -90,7 +96,7 @@ func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error query = fmt.Sprintf("%s WITH USER %s", query, strings.Join(usernamesSafe, ", ")) } - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("could not create redshift group: %w", err) } @@ -101,12 +107,22 @@ func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error d.SetId(groSysID) + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftGroupReadImpl(db, d) } func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error { groupName := d.Get(groupNameAttr).(string) + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + rows, err := db.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") if err != nil { return err @@ -119,34 +135,44 @@ func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error return err } - if _, err := db.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { + if _, err := tx.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { return err } - if _, err := db.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { + if _, err := tx.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM GROUP %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(groupName))); err != nil { return err } } - if _, err := db.Exec(fmt.Sprintf("DROP GROUP %s", pq.QuoteIdentifier(groupName))); err != nil { + if _, err := tx.Exec(fmt.Sprintf("DROP GROUP %s", pq.QuoteIdentifier(groupName))); err != nil { return err } - return nil + return tx.Commit() } func resourceRedshiftGroupUpdate(db *DBConnection, d *schema.ResourceData) error { - if err := setGroupName(db, d); err != nil { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + + if err := setGroupName(tx, d); err != nil { return err } - if err := setUsersNames(db, d); err != nil { + if err := setUsersNames(tx, db, d); err != nil { return err } + if err := tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftGroupReadImpl(db, d) } -func setGroupName(db *DBConnection, d *schema.ResourceData) error { +func setGroupName(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(groupNameAttr) { return nil } @@ -160,7 +186,7 @@ func setGroupName(db *DBConnection, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER GROUP %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating Group NAME: %w", err) } @@ -182,7 +208,7 @@ func checkIfUserExists(db *sql.DB, name string) (bool, error) { return true, nil } -func setUsersNames(db *DBConnection, d *schema.ResourceData) error { +func setUsersNames(tx *sql.Tx, db *DBConnection, d *schema.ResourceData) error { if !d.HasChange(groupUsersAttr) { return nil } @@ -208,7 +234,7 @@ func setUsersNames(db *DBConnection, d *schema.ResourceData) error { if len(removedUsersNamesSafe) > 0 { query := fmt.Sprintf("ALTER GROUP %s DROP USER %s", pq.QuoteIdentifier(groupName), strings.Join(removedUsersNamesSafe, ", ")) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return err } } @@ -222,7 +248,7 @@ func setUsersNames(db *DBConnection, d *schema.ResourceData) error { query := fmt.Sprintf("ALTER GROUP %s ADD USER %s", pq.QuoteIdentifier(groupName), strings.Join(addedUsersNamesSafe, ", ")) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return err } } diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index 422d452b..58159bd2 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -567,6 +567,11 @@ func resourceRedshiftSchemaReadExternal(db *DBConnection, d *schema.ResourceData } func resourceRedshiftSchemaDelete(db *DBConnection, d *schema.ResourceData) error { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) schemaName := d.Get(schemaNameAttr).(string) cascadeOrRestrict := "RESTRICT" @@ -575,28 +580,37 @@ func resourceRedshiftSchemaDelete(db *DBConnection, d *schema.ResourceData) erro } query := fmt.Sprintf("DROP SCHEMA %s %s", pq.QuoteIdentifier(schemaName), cascadeOrRestrict) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return err } - return nil + return tx.Commit() } func resourceRedshiftSchemaCreate(db *DBConnection, d *schema.ResourceData) error { - var err error + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + if _, isExternal := d.GetOk(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")); isExternal { - err = resourceRedshiftSchemaCreateExternal(db, d) + err = resourceRedshiftSchemaCreateExternal(tx, db.DB, d) } else { - err = resourceRedshiftSchemaCreateInternal(db, d) + err = resourceRedshiftSchemaCreateInternal(tx, db.DB, d) } if err != nil { return err } + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftSchemaReadImpl(db, d) } -func resourceRedshiftSchemaCreateInternal(db *DBConnection, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) schemaQuota := d.Get(schemaQuotaAttr).(int) var createOpts []string @@ -613,7 +627,7 @@ func resourceRedshiftSchemaCreateInternal(db *DBConnection, d *schema.ResourceDa query := fmt.Sprintf("CREATE SCHEMA %s %s", pq.QuoteIdentifier(schemaName), strings.Join(createOpts, " ")) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return err } @@ -627,7 +641,7 @@ func resourceRedshiftSchemaCreateInternal(db *DBConnection, d *schema.ResourceDa return nil } -func resourceRedshiftSchemaCreateExternal(db *DBConnection, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) query := fmt.Sprintf("CREATE EXTERNAL SCHEMA %s", pq.QuoteIdentifier(schemaName)) sourceDbName := d.Get(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")).(string) @@ -654,14 +668,14 @@ func resourceRedshiftSchemaCreateExternal(db *DBConnection, d *schema.ResourceDa query = fmt.Sprintf("%s %s", query, configQuery) log.Printf("[DEBUG] creating external schema: %s\n", query) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return err } if v, ok := d.GetOk(schemaOwnerAttr); ok { query = fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(v.(string))) log.Printf("[DEBUG] setting schema owner: %s\n", query) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return err } } @@ -766,22 +780,32 @@ func getRedshiftConfigQueryPart(d *schema.ResourceData, sourceDbName string) str } func resourceRedshiftSchemaUpdate(db *DBConnection, d *schema.ResourceData) error { - if err := setSchemaName(db, d); err != nil { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + + if err := setSchemaName(tx, d); err != nil { return err } - if err := setSchemaOwner(db, db, d); err != nil { + if err := setSchemaOwner(tx, db, d); err != nil { return err } - if err := setSchemaQuota(db, d); err != nil { + if err := setSchemaQuota(tx, d); err != nil { return err } + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftSchemaReadImpl(db, d) } -func setSchemaName(db *DBConnection, d *schema.ResourceData) error { +func setSchemaName(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(schemaNameAttr) { return nil } @@ -795,14 +819,14 @@ func setSchemaName(db *DBConnection, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER SCHEMA %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating schema NAME: %w", err) } return nil } -func setSchemaOwner(db *DBConnection, _ *DBConnection, d *schema.ResourceData) error { +func setSchemaOwner(tx *sql.Tx, _ *DBConnection, d *schema.ResourceData) error { if !d.HasChange(schemaOwnerAttr) { return nil } @@ -810,11 +834,11 @@ func setSchemaOwner(db *DBConnection, _ *DBConnection, d *schema.ResourceData) e schemaName := d.Get(schemaNameAttr).(string) schemaOwner := d.Get(schemaOwnerAttr).(string) - _, err := db.Exec(fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(schemaOwner))) + _, err := tx.Exec(fmt.Sprintf("ALTER SCHEMA %s OWNER TO %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(schemaOwner))) return err } -func setSchemaQuota(db *DBConnection, d *schema.ResourceData) error { +func setSchemaQuota(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(schemaQuotaAttr) { return nil } @@ -827,6 +851,6 @@ func setSchemaQuota(db *DBConnection, d *schema.ResourceData) error { quotaValue = fmt.Sprintf("%d GB", schemaQuota) } - _, err := db.Exec(fmt.Sprintf("ALTER SCHEMA %s QUOTA %s", pq.QuoteIdentifier(schemaName), quotaValue)) + _, err := tx.Exec(fmt.Sprintf("ALTER SCHEMA %s QUOTA %s", pq.QuoteIdentifier(schemaName), quotaValue)) return err } diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index 53aee40f..e3061027 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -140,6 +140,12 @@ Amazon Redshift user accounts can only be created and dropped by a database supe } func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + stringOpts := []struct { hclKey string sqlKey string @@ -227,7 +233,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error createStr := strings.Join(createOpts, " ") query := fmt.Sprintf("CREATE USER %s WITH %s", pq.QuoteIdentifier(userName), createStr) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error creating user %s: %w", userName, err) } @@ -238,6 +244,10 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error d.SetId(usesysid) + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftUserReadImpl(db, d) } @@ -317,6 +327,12 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error userName := d.Get(userNameAttr).(string) newOwnerName := permanentUsername(db.client.config.Username) + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + // Based on https://github.com/awslabs/amazon-redshift-utils/blob/master/src/AdminViews/v_find_dropuser_objs.sql var reassignOwnerGenerator = `SELECT owner.ddl FROM ( @@ -372,7 +388,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error } for _, statement := range reassignStatements { - if _, err := db.Exec(statement); err != nil { + if _, err := tx.Exec(statement); err != nil { log.Printf("error: %#v", err) return err } @@ -390,59 +406,74 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error return err } - if _, err := db.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { + if _, err := tx.Exec(fmt.Sprintf("REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { return err } - if _, err := db.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { + if _, err := tx.Exec(fmt.Sprintf("ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE ALL ON TABLES FROM %s", pq.QuoteIdentifier(schemaName), pq.QuoteIdentifier(userName))); err != nil { return err } } - if _, err := db.Exec(fmt.Sprintf("DROP USER %s", pq.QuoteIdentifier(userName))); err != nil { + if _, err := tx.Exec(fmt.Sprintf("DROP USER %s", pq.QuoteIdentifier(userName))); err != nil { return err } + if err := tx.Commit(); err != nil { + return err + //return fmt.Errorf("could not commit transaction: %w", err) + } + return nil } func resourceRedshiftUserUpdate(db *DBConnection, d *schema.ResourceData) error { - if err := setUserName(db, d); err != nil { + tx, err := startTransaction(db.client, "") + if err != nil { return err } + defer deferredRollback(tx) - if err := setUserPassword(db, d); err != nil { + if err := setUserName(tx, d); err != nil { return err } - if err := setUserConnLimit(db, d); err != nil { + if err := setUserPassword(tx, d); err != nil { return err } - if err := setUserCreateDB(db, d); err != nil { + if err := setUserConnLimit(tx, d); err != nil { return err } - if err := setUserSuperuser(db, d); err != nil { + + if err := setUserCreateDB(tx, d); err != nil { + return err + } + if err := setUserSuperuser(tx, d); err != nil { return err } - if err := setUserValidUntil(db, d); err != nil { + if err := setUserValidUntil(tx, d); err != nil { return err } - if err := setUserSyslogAccess(db, d); err != nil { + if err := setUserSyslogAccess(tx, d); err != nil { return err } - if err := setUserSessionTimeout(db, d); err != nil { + if err := setUserSessionTimeout(tx, d); err != nil { return err } + if err := tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftUserReadImpl(db, d) } -func setUserName(db *DBConnection, d *schema.ResourceData) error { +func setUserName(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(userNameAttr) { return nil } @@ -456,14 +487,14 @@ func setUserName(db *DBConnection, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER USER %s RENAME TO %s", pq.QuoteIdentifier(oldValue), pq.QuoteIdentifier(newValue)) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating User NAME: %w", err) } return nil } -func setUserPassword(db *DBConnection, d *schema.ResourceData) error { +func setUserPassword(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(userPasswordAttr) && !d.HasChange(userNameAttr) { return nil } @@ -477,13 +508,13 @@ func setUserPassword(db *DBConnection, d *schema.ResourceData) error { } query := fmt.Sprintf("ALTER USER %s %s", pq.QuoteIdentifier(userName), passwdTok) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating user password: %w", err) } return nil } -func setUserConnLimit(db *DBConnection, d *schema.ResourceData) error { +func setUserConnLimit(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(userConnLimitAttr) { return nil } @@ -491,14 +522,14 @@ func setUserConnLimit(db *DBConnection, d *schema.ResourceData) error { connLimit := d.Get(userConnLimitAttr).(int) userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s CONNECTION LIMIT %d", pq.QuoteIdentifier(userName), connLimit) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating user CONNECTION LIMIT: %w", err) } return nil } -func setUserSessionTimeout(db *DBConnection, d *schema.ResourceData) error { +func setUserSessionTimeout(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(userSessionTimeoutAttr) { return nil } @@ -511,14 +542,14 @@ func setUserSessionTimeout(db *DBConnection, d *schema.ResourceData) error { } else { query = fmt.Sprintf("ALTER USER %s SESSION TIMEOUT %d", pq.QuoteIdentifier(userName), sessionTimeout) } - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating user SESSION TIMEOUT: %w", err) } return nil } -func setUserCreateDB(db *DBConnection, d *schema.ResourceData) error { +func setUserCreateDB(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(userCreateDBAttr) { return nil } @@ -530,14 +561,14 @@ func setUserCreateDB(db *DBConnection, d *schema.ResourceData) error { } userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s WITH %s", pq.QuoteIdentifier(userName), tok) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating user CREATEDB: %w", err) } return nil } -func setUserSuperuser(db *DBConnection, d *schema.ResourceData) error { +func setUserSuperuser(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(userSuperuserAttr) { return nil } @@ -549,14 +580,14 @@ func setUserSuperuser(db *DBConnection, d *schema.ResourceData) error { } userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s WITH %s", pq.QuoteIdentifier(userName), tok) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating user SUPERUSER: %w", err) } return nil } -func setUserValidUntil(db *DBConnection, d *schema.ResourceData) error { +func setUserValidUntil(tx *sql.Tx, d *schema.ResourceData) error { if !d.HasChange(userValidUntilAttr) { return nil } @@ -570,14 +601,14 @@ func setUserValidUntil(db *DBConnection, d *schema.ResourceData) error { userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s VALID UNTIL '%s'", pq.QuoteIdentifier(userName), pqQuoteLiteral(validUntil)) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating user VALID UNTIL: %w", err) } return nil } -func setUserSyslogAccess(db *DBConnection, d *schema.ResourceData) error { +func setUserSyslogAccess(tx *sql.Tx, d *schema.ResourceData) error { syslogAccessCurrent := d.Get(userSyslogAccessAttr).(string) syslogAccessComputed := syslogAccessCurrent if syslogAccessComputed == "" { @@ -594,7 +625,7 @@ func setUserSyslogAccess(db *DBConnection, d *schema.ResourceData) error { userName := d.Get(userNameAttr).(string) query := fmt.Sprintf("ALTER USER %s WITH SYSLOG ACCESS %s", pq.QuoteIdentifier(userName), syslogAccessComputed) - if _, err := db.Exec(query); err != nil { + if _, err := tx.Exec(query); err != nil { return fmt.Errorf("error updating user SYSLOG ACCESS: %w", err) } From 2981dfa97a6a94362402781ad792932069b9bc09 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 14 Aug 2025 11:39:13 +0200 Subject: [PATCH 13/34] Revert "do not query in transactions to enable usage of aws redshift data api" This reverts commit 5dc34c5f8975160445b274ad8db24f6c495de268. --- redshift/helpers.go | 12 ++++---- redshift/resource_redshift_datashare.go | 30 +++++++++++++++---- .../resource_redshift_default_privileges.go | 25 +++++++++++----- redshift/resource_redshift_group.go | 12 ++++---- redshift/resource_redshift_schema.go | 12 ++++---- redshift/resource_redshift_user.go | 6 ++-- redshift/resource_redshift_user_test.go | 14 ++------- 7 files changed, 65 insertions(+), 46 deletions(-) diff --git a/redshift/helpers.go b/redshift/helpers.go index f709e975..c8c15c0b 100644 --- a/redshift/helpers.go +++ b/redshift/helpers.go @@ -69,18 +69,18 @@ func pqQuoteLiteral(in string) string { return in } -func getGroupIDFromName(db *sql.DB, group string) (groupID int, err error) { - err = db.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", group).Scan(&groupID) +func getGroupIDFromName(tx *sql.Tx, group string) (groupID int, err error) { + err = tx.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", group).Scan(&groupID) return } -func getUserIDFromName(db *sql.DB, user string) (userID int, err error) { - err = db.QueryRow("SELECT usesysid FROM pg_user WHERE usename = $1", user).Scan(&userID) +func getUserIDFromName(tx *sql.Tx, user string) (userID int, err error) { + err = tx.QueryRow("SELECT usesysid FROM pg_user WHERE usename = $1", user).Scan(&userID) return } -func getSchemaIDFromName(db *sql.DB, schema string) (schemaID int, err error) { - err = db.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", schema).Scan(&schemaID) +func getSchemaIDFromName(tx *sql.Tx, schema string) (schemaID int, err error) { + err = tx.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", schema).Scan(&schemaID) return } diff --git a/redshift/resource_redshift_datashare.go b/redshift/resource_redshift_datashare.go index 7951667e..2a053dcd 100644 --- a/redshift/resource_redshift_datashare.go +++ b/redshift/resource_redshift_datashare.go @@ -114,7 +114,7 @@ func resourceRedshiftDatashareCreate(db *DBConnection, d *schema.ResourceData) e var shareId string query = "SELECT share_id FROM SVV_DATASHARES WHERE share_type = 'OUTBOUND' AND share_name = $1" log.Printf("[DEBUG] %s, $1=%s\n", query, strings.ToLower(shareName)) - if err := db.DB.QueryRow(query, strings.ToLower(shareName)).Scan(&shareId); err != nil { + if err := tx.QueryRow(query, strings.ToLower(shareName)).Scan(&shareId); err != nil { return err } @@ -261,7 +261,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err WHERE share_type = 'OUTBOUND' AND share_id = $1` log.Printf("[DEBUG] %s, $1=%s\n", query, d.Id()) - err = db.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) + err = tx.QueryRow(query, d.Id()).Scan(&shareName, &owner, &publicAccessible, &producerAccount, &producerNamespace, &created) if err != nil { return err } @@ -273,7 +273,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err d.Set(dataShareProducerNamespaceAttr, producerNamespace) d.Set(dataShareCreatedAttr, created) - if err = readDatashareSchemas(db.DB, shareName, d); err != nil { + if err = readDatashareSchemas(tx, shareName, d); err != nil { return err } @@ -284,7 +284,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err return nil } -func readDatashareSchemas(db *sql.DB, shareName string, d *schema.ResourceData) error { +func readDatashareSchemas(tx *sql.Tx, shareName string, d *schema.ResourceData) error { query := ` SELECT object_name @@ -294,7 +294,7 @@ func readDatashareSchemas(db *sql.DB, shareName string, d *schema.ResourceData) AND share_name = $1 ` log.Printf("[DEBUG] %s, $1=%s\n", query, shareName) - rows, err := db.Query(query, shareName) + rows, err := tx.Query(query, shareName) if err != nil { return err } @@ -331,6 +331,10 @@ func resourceRedshiftDatashareUpdate(db *DBConnection, d *schema.ResourceData) e return err } + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return resourceRedshiftDatashareRead(db, d) } @@ -401,9 +405,15 @@ func setDatashareSchemas(tx *sql.Tx, d *schema.ResourceData) error { } func resourceRedshiftDatashareDelete(db *DBConnection, d *schema.ResourceData) error { + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + var shareName string query := "SELECT share_name FROM svv_datashares WHERE share_type='OUTBOUND' AND share_id=$1" - if err := db.QueryRow(query, d.Id()).Scan(&shareName); err != nil { + if err := tx.QueryRow(query, d.Id()).Scan(&shareName); err != nil { if errors.Is(err, sql.ErrNoRows) { log.Printf("[WARN] data share with id %s does not exist.\n", d.Id()) return nil @@ -412,5 +422,13 @@ func resourceRedshiftDatashareDelete(db *DBConnection, d *schema.ResourceData) e } query = fmt.Sprintf("DROP DATASHARE %s", pq.QuoteIdentifier(shareName)) log.Printf("[DEBUG] %s\n", query) + _, err = tx.Exec(query) + if err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } return nil } diff --git a/redshift/resource_redshift_default_privileges.go b/redshift/resource_redshift_default_privileges.go index 9f7de8cc..86ac1f7a 100644 --- a/redshift/resource_redshift_default_privileges.go +++ b/redshift/resource_redshift_default_privileges.go @@ -162,11 +162,16 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou schemaName, schemaNameSet := d.GetOk(defaultPrivilegesSchemaAttr) ownerName := d.Get(defaultPrivilegesOwnerAttr).(string) - var err error + tx, err := startTransaction(db.client, "") + if err != nil { + return err + } + defer deferredRollback(tx) + schemaID := defaultPrivilegesAllSchemasID if schemaNameSet { log.Printf("[DEBUG] getting ID for schema %s\n", schemaName) - schemaID, err = getSchemaIDFromName(db.DB, schemaName.(string)) + schemaID, err = getSchemaIDFromName(tx, schemaName.(string)) if err != nil { return fmt.Errorf("failed to get schema ID for schema '%s': %w", schemaName, err) } @@ -174,14 +179,14 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou if groupName, groupNameSet := d.GetOk(defaultPrivilegesGroupAttr); groupNameSet { log.Printf("[DEBUG] getting ID for group %s\n", groupName.(string)) - entityID, err = getGroupIDFromName(db.DB, groupName.(string)) + entityID, err = getGroupIDFromName(tx, groupName.(string)) entityIsUser = false if err != nil { return fmt.Errorf("failed to get group ID: %w", err) } } else if userName, userNameSet := d.GetOk(defaultPrivilegesUserAttr); userNameSet { log.Printf("[DEBUG] getting ID for user %s\n", userName.(string)) - entityID, err = getUserIDFromName(db.DB, userName.(string)) + entityID, err = getUserIDFromName(tx, userName.(string)) entityIsUser = true if err != nil { return fmt.Errorf("failed to get user ID: %w", err) @@ -189,7 +194,7 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou } log.Printf("[DEBUG] getting ID for owner %s\n", ownerName) - ownerID, err := getUserIDFromName(db.DB, ownerName) + ownerID, err := getUserIDFromName(tx, ownerName) if err != nil { return fmt.Errorf("failed to get user ID: %w", err) } @@ -197,15 +202,19 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou switch strings.ToUpper(d.Get(defaultPrivilegesObjectTypeAttr).(string)) { case "TABLE": log.Println("[DEBUG] reading default privileges") - if err := readGroupTableDefaultPrivileges(db.DB, d, entityID, schemaID, ownerID, entityIsUser); err != nil { + if err := readGroupTableDefaultPrivileges(tx, d, entityID, schemaID, ownerID, entityIsUser); err != nil { return fmt.Errorf("failed to read table privileges: %w", err) } } + if err := tx.Commit(); err != nil { + return fmt.Errorf("could not commit transaction: %w", err) + } + return nil } -func readGroupTableDefaultPrivileges(db *sql.DB, d *schema.ResourceData, entityID, schemaID, ownerID int, entityIsUser bool) error { +func readGroupTableDefaultPrivileges(tx *sql.Tx, d *schema.ResourceData, entityID, schemaID, ownerID int, entityIsUser bool) error { var tableSelect, tableUpdate, tableInsert, tableDelete, tableDrop, tableReferences, tableRule, tableTrigger bool var query string @@ -249,7 +258,7 @@ func readGroupTableDefaultPrivileges(db *sql.DB, d *schema.ResourceData, entityI ` } - if err := db.QueryRow(query, schemaID, entityID, defaultPrivilegesObjectTypesCodes["table"], ownerID).Scan( + if err := tx.QueryRow(query, schemaID, entityID, defaultPrivilegesObjectTypesCodes["table"], ownerID).Scan( &tableSelect, &tableUpdate, &tableInsert, diff --git a/redshift/resource_redshift_group.go b/redshift/resource_redshift_group.go index 73c40945..9cf70df1 100644 --- a/redshift/resource_redshift_group.go +++ b/redshift/resource_redshift_group.go @@ -101,7 +101,7 @@ func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error } var groSysID string - if err := db.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", strings.ToLower(groupName)).Scan(&groSysID); err != nil { + if err := tx.QueryRow("SELECT grosysid FROM pg_group WHERE groname = $1", strings.ToLower(groupName)).Scan(&groSysID); err != nil { return fmt.Errorf("could not get redshift group id for %q: %w", groupName, err) } @@ -123,7 +123,7 @@ func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error } defer deferredRollback(tx) - rows, err := db.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") + rows, err := tx.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") if err != nil { return err } @@ -193,10 +193,10 @@ func setGroupName(tx *sql.Tx, d *schema.ResourceData) error { return nil } -func checkIfUserExists(db *sql.DB, name string) (bool, error) { +func checkIfUserExists(tx *sql.Tx, name string) (bool, error) { var result int - err := db.QueryRow("SELECT 1 FROM pg_user_info WHERE usename=$1", name).Scan(&result) + err := tx.QueryRow("SELECT 1 FROM pg_user_info WHERE usename=$1", name).Scan(&result) switch { case errors.Is(err, sql.ErrNoRows): @@ -208,7 +208,7 @@ func checkIfUserExists(db *sql.DB, name string) (bool, error) { return true, nil } -func setUsersNames(tx *sql.Tx, db *DBConnection, d *schema.ResourceData) error { +func setUsersNames(tx *sql.Tx, _ *DBConnection, d *schema.ResourceData) error { if !d.HasChange(groupUsersAttr) { return nil } @@ -221,7 +221,7 @@ func setUsersNames(tx *sql.Tx, db *DBConnection, d *schema.ResourceData) error { if removedUsers.Len() > 0 { var removedUsersNamesSafe []string for _, name := range removedUsers.List() { - userExists, err := checkIfUserExists(db.DB, name.(string)) + userExists, err := checkIfUserExists(tx, name.(string)) if err != nil { return err } diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index 58159bd2..1a2f239e 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -595,9 +595,9 @@ func resourceRedshiftSchemaCreate(db *DBConnection, d *schema.ResourceData) erro defer deferredRollback(tx) if _, isExternal := d.GetOk(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")); isExternal { - err = resourceRedshiftSchemaCreateExternal(tx, db.DB, d) + err = resourceRedshiftSchemaCreateExternal(tx, d) } else { - err = resourceRedshiftSchemaCreateInternal(tx, db.DB, d) + err = resourceRedshiftSchemaCreateInternal(tx, d) } if err != nil { return err @@ -610,7 +610,7 @@ func resourceRedshiftSchemaCreate(db *DBConnection, d *schema.ResourceData) erro return resourceRedshiftSchemaReadImpl(db, d) } -func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) schemaQuota := d.Get(schemaQuotaAttr).(int) var createOpts []string @@ -632,7 +632,7 @@ func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.Reso } var schemaOID string - if err := db.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { + if err := tx.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { return err } @@ -641,7 +641,7 @@ func resourceRedshiftSchemaCreateInternal(tx *sql.Tx, db *sql.DB, d *schema.Reso return nil } -func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, db *sql.DB, d *schema.ResourceData) error { +func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, d *schema.ResourceData) error { schemaName := d.Get(schemaNameAttr).(string) query := fmt.Sprintf("CREATE EXTERNAL SCHEMA %s", pq.QuoteIdentifier(schemaName)) sourceDbName := d.Get(fmt.Sprintf("%s.0.%s", schemaExternalSchemaAttr, "database_name")).(string) @@ -681,7 +681,7 @@ func resourceRedshiftSchemaCreateExternal(tx *sql.Tx, db *sql.DB, d *schema.Reso } var schemaOID string - if err := db.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { + if err := tx.QueryRow("SELECT oid FROM pg_namespace WHERE nspname = $1", strings.ToLower(schemaName)).Scan(&schemaOID); err != nil { return err } diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index e3061027..8312a093 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -238,7 +238,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error } var usesysid string - if err := db.QueryRow("SELECT usesysid FROM pg_user_info WHERE usename = $1", userName).Scan(&usesysid); err != nil { + if err := tx.QueryRow("SELECT usesysid FROM pg_user_info WHERE usename = $1", userName).Scan(&usesysid); err != nil { return fmt.Errorf("user does not exist in pg_user_info table: %w", err) } @@ -371,7 +371,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error OWNER("userid", "ddl") WHERE owner.userid = $1;` - rows, err := db.Query(reassignOwnerGenerator, useSysID, pq.QuoteIdentifier(newOwnerName)) + rows, err := tx.Query(reassignOwnerGenerator, useSysID, pq.QuoteIdentifier(newOwnerName)) if err != nil { return err } @@ -394,7 +394,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error } } - rows, err = db.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") + rows, err = tx.Query("SELECT nspname FROM pg_namespace WHERE nspowner != 1 OR nspname = 'public'") if err != nil { return err } diff --git a/redshift/resource_redshift_user_test.go b/redshift/resource_redshift_user_test.go index 3d0533af..aca7d6f8 100644 --- a/redshift/resource_redshift_user_test.go +++ b/redshift/resource_redshift_user_test.go @@ -487,7 +487,7 @@ func TestPermanentUsername(t *testing.T) { } func testAccCheckRedshiftUserCanLogin(user string, password string) resource.TestCheckFunc { - return func(s *terraform.State) (err error) { + return func(s *terraform.State) error { // there doesn't seem to be a good way to extract the provider configuration // at runtime. However we know we've configured the provider with default settings // so we can mimic the same behavior @@ -517,19 +517,11 @@ func testAccCheckRedshiftUserCanLogin(user string, password string) resource.Tes MaxConns: defaultProviderMaxOpenConnections, } - client := config.NewClient(database) - var conn *DBConnection - conn, err = client.Connect() + client, err := config.Client() if err != nil { return fmt.Errorf("user is unable to login: %w", err) } defer client.Close() - defer func(conn *DBConnection) { - closeErr := conn.Close() - if err == nil { - err = closeErr - } - }(conn) - return + return nil } } From c9ae9fbb5002de8b91d7853734015a24fbab6c3b Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 18 Aug 2025 11:35:08 +0200 Subject: [PATCH 14/34] add logic to handle "infinity" value for redshift data api conn --- redshift/resource_redshift_user.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index 8312a093..875c037b 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -299,6 +299,12 @@ func resourceRedshiftUserReadImpl(db *DBConnection, d *schema.ResourceData) erro case err != nil: return fmt.Errorf("error reading User: %w", err) } + + userValidUntil, err = validateAndAdjustValidUntil(userValidUntil) + if err != nil { + return err + } + userConnLimitNumber := -1 if userConnLimit != "UNLIMITED" { if userConnLimitNumber, err = strconv.Atoi(userConnLimit); err != nil { @@ -322,6 +328,25 @@ func resourceRedshiftUserReadImpl(db *DBConnection, d *schema.ResourceData) erro return nil } +const redshiftDataApiInfinityDateString = "2038-01-19 03:14:04" + +var redshiftDataApiDatetimeRegexp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$`) +var correctDatetimeRegexp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\+00$`) + +func validateAndAdjustValidUntil(validUntil string) (string, error) { + if validUntil == redshiftDataApiInfinityDateString { + // The Redshift Data API translates the `infinity` to a date in 2038 (see https://en.wikipedia.org/wiki/Year_2038_problem) + return "infinity", nil + } else if redshiftDataApiDatetimeRegexp.MatchString(validUntil) { + // The Redshift Data API returns the datetime without the timezone offset, so we need to add it + validUntil += "+00" + } + if !correctDatetimeRegexp.MatchString(validUntil) { + return "", fmt.Errorf(`received invalid date format for valid_until: %q, expected format is "YYYY-MM-DD HH:MM:SS+00"`, validUntil) + } + return validUntil, nil +} + func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error { useSysID := d.Id() userName := d.Get(userNameAttr).(string) From 826c42a1c84803fc3e4580db86e82db25e82cd09 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 18 Aug 2025 11:36:37 +0200 Subject: [PATCH 15/34] fix tests adjust expected error messages and add depends on statements --- redshift/resource_redshift_grant_test.go | 8 +++ redshift/resource_redshift_user_test.go | 72 +++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/redshift/resource_redshift_grant_test.go b/redshift/resource_redshift_grant_test.go index 71b422f1..10a2e8df 100644 --- a/redshift/resource_redshift_grant_test.go +++ b/redshift/resource_redshift_grant_test.go @@ -25,6 +25,10 @@ resource "redshift_grant" "public" { schema = %[1]q object_type = "schema" privileges = ["create", "usage"] + + depends_on = [ + redshift_schema.test + ] } # Add user with different privileges to see if we do not catch them by accident @@ -37,6 +41,10 @@ resource "redshift_grant" "user" { schema = %[1]q object_type = "schema" privileges = ["usage"] + + depends_on = [ + redshift_schema.test + ] } `, schemaName, userName) diff --git a/redshift/resource_redshift_user_test.go b/redshift/resource_redshift_user_test.go index aca7d6f8..665674fd 100644 --- a/redshift/resource_redshift_user_test.go +++ b/redshift/resource_redshift_user_test.go @@ -5,13 +5,14 @@ import ( "database/sql" "errors" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "os" "regexp" "strconv" "strings" "testing" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -274,7 +275,7 @@ func TestAccRedshiftUser_SuperuserSyslogAccess(t *testing.T) { "(superuser) RESTRICTED syslog access": { isSuperuser: true, syslogAccess: defaultUserSyslogAccess, - expectError: regexp.MustCompile("Superusers must have syslog access set to UNRESTRICTED."), + expectError: regexp.MustCompile("superusers must have syslog access set to \"UNRESTRICTED\""), }, "(superuser) UNRESTRICTED syslog access": { isSuperuser: true, @@ -517,7 +518,7 @@ func testAccCheckRedshiftUserCanLogin(user string, password string) resource.Tes MaxConns: defaultProviderMaxOpenConnections, } - client, err := config.Client() + client := config.NewClient(database) if err != nil { return fmt.Errorf("user is unable to login: %w", err) } @@ -525,3 +526,68 @@ func testAccCheckRedshiftUserCanLogin(user string, password string) resource.Tes return nil } } + +func Test_validateAndAdjustValidUntil(t *testing.T) { + type args struct { + validUntil string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "translate Redshift Data API infinity date", + args: args{ + validUntil: "2038-01-19 03:14:04", + }, + want: "infinity", + wantErr: false, + }, + { + name: "adds suffix to Redshift Data API datetime", + args: args{ + validUntil: "2025-08-06 17:22:56", + }, + want: "2025-08-06 17:22:56+00", + wantErr: false, + }, + { + name: "does not add suffix to correct datetime", + args: args{ + validUntil: "2025-08-06 17:22:56+00", + }, + want: "2025-08-06 17:22:56+00", + wantErr: false, + }, + { + name: "returns error for invalid timezone", + args: args{ + validUntil: "2025-08-06 17:22:56+01", + }, + want: "", + wantErr: true, + }, + { + name: "returns error for invalid datetime", + args: args{ + validUntil: "some none date", + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validateAndAdjustValidUntil(tt.args.validUntil) + if (err != nil) != tt.wantErr { + t.Errorf("validateAndAdjustValidUntil() error = %v, wantErr = %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("validateAndAdjustValidUntil() got = %v, want %v", got, tt.want) + } + }) + } +} From 339a5bf43f9fd7bda85e47bfd277ab281a1f1acf Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 18 Aug 2025 11:52:51 +0200 Subject: [PATCH 16/34] update dependencies --- go.mod | 64 ++++++++++++++++++++-------------------- go.sum | 93 ++++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 103 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 9d02a636..29d4133a 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,20 @@ module github.com/dbsystel/terraform-provider-redshift -go 1.23.7 +go 1.24 toolchain go1.24.5 require ( - github.com/aws/aws-sdk-go-v2 v1.37.2 - github.com/aws/aws-sdk-go-v2/config v1.30.3 - github.com/aws/aws-sdk-go-v2/credentials v1.18.3 - github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.36.0 + github.com/aws/aws-sdk-go-v2 v1.38.0 + github.com/aws/aws-sdk-go-v2/config v1.31.0 + github.com/aws/aws-sdk-go-v2/credentials v1.18.4 + github.com/aws/aws-sdk-go-v2/service/redshift v1.57.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 github.com/hashicorp/terraform-plugin-docs v0.22.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 github.com/lib/pq v1.10.9 - github.com/mmichaelb/redshift-data-sql-driver v0.0.3 - golang.org/x/net v0.42.0 + github.com/mmichaelb/redshift-data-sql-driver v0.4.0 + golang.org/x/net v0.43.0 ) require ( @@ -24,23 +24,23 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/agext/levenshtein v1.2.2 // indirect + github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.2 // indirect - github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.35.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.32.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.36.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -51,20 +51,20 @@ require ( github.com/hashicorp/go-cty v1.5.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-plugin v1.7.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hc-install v0.9.2 // indirect - github.com/hashicorp/hcl/v2 v2.23.0 // indirect + github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.23.0 // indirect github.com/hashicorp/terraform-json v0.25.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.27.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.28.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect - github.com/hashicorp/terraform-registry-address v0.2.5 // indirect + github.com/hashicorp/terraform-registry-address v0.3.0 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect - github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/mattn/go-colorable v0.1.14 // indirect @@ -72,10 +72,10 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/oklog/run v1.0.0 // indirect + github.com/oklog/run v1.2.0 // indirect github.com/posener/complete v1.2.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.5.0 // indirect @@ -86,17 +86,17 @@ require ( github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.16.3 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect - golang.org/x/crypto v0.40.0 // indirect + golang.org/x/crypto v0.41.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect - golang.org/x/mod v0.25.0 // indirect + golang.org/x/mod v0.27.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.34.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.36.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/grpc v1.72.1 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect + google.golang.org/grpc v1.74.2 // indirect + google.golang.org/protobuf v1.36.7 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f2075940..d01c2097 100644 --- a/go.sum +++ b/go.sum @@ -16,39 +16,43 @@ github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNx github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo= -github.com/aws/aws-sdk-go-v2 v1.37.2/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= -github.com/aws/aws-sdk-go-v2/config v1.30.3 h1:utupeVnE3bmB221W08P0Moz1lDI3OwYa2fBtUhl7TCc= -github.com/aws/aws-sdk-go-v2/config v1.30.3/go.mod h1:NDGwOEBdpyZwLPlQkpKIO7frf18BW8PaCmAM9iUxQmI= -github.com/aws/aws-sdk-go-v2/credentials v1.18.3 h1:ptfyXmv+ooxzFwyuBth0yqABcjVIkjDL0iTYZBSbum8= -github.com/aws/aws-sdk-go-v2/credentials v1.18.3/go.mod h1:Q43Nci++Wohb0qUh4m54sNln0dbxJw8PvQWkrwOkGOI= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 h1:nRniHAvjFJGUCl04F3WaAj7qp/rcz5Gi1OVoj5ErBkc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2/go.mod h1:eJDFKAMHHUvv4a0Zfa7bQb//wFNUXGrbFpYRCHe2kD0= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 h1:sPiRHLVUIIQcoVZTNwqQcdtjkqkPopyYmIX0M5ElRf4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2/go.mod h1:ik86P3sgV+Bk7c1tBFCwI3VxMoSEwl4YkRB9xn1s340= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 h1:ZdzDAg075H6stMZtbD2o+PyB933M/f20e9WmCBC17wA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2/go.mod h1:eE1IIzXG9sdZCB0pNNpMpsYTLl4YdOQD3njiVN1e/E4= +github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU= +github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4= +github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I= +github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM= +github.com/aws/aws-sdk-go-v2/credentials v1.18.4/go.mod h1:nwg78FjH2qvsRM1EVZlX9WuGUJOL5od+0qvm0adEzHk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2elW6CGMSYKhdozE7/Svh78= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.2 h1:oxmDEO14NBZJbK/M8y3brhMFEIGN4j8a6Aq8eY0sqlo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.2/go.mod h1:4hH+8QCrk1uRWDPsVfsNDUup3taAjO8Dnx63au7smAU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 h1:ieRzyHXypu5ByllM7Sp4hC5f/1Fy5wqxqY0yB85hC7s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3/go.mod h1:O5ROz8jHiOAKAwx179v+7sHMhfobFVi6nZt8DEyiYoM= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0 h1:70T8EpAmUAmh1+iljlPu94NnUKATN9GedtKY0y9I4CY= github.com/aws/aws-sdk-go-v2/service/redshift v1.55.0/go.mod h1:ItDt61dKOBnzf5gY/kvu4UaDKNxdp8LntwS7PaaVpfU= -github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.35.0 h1:2uGuWyvd7uCOEcEMN+SWkJsXlXOPrzfBtElITMnVAp4= -github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.35.0/go.mod h1:6Xy8SN1liN5+zoTMZ1C2UpeUdJURWSqr9u8wkOtSpdE= -github.com/aws/aws-sdk-go-v2/service/sso v1.27.0 h1:j7/jTOjWeJDolPwZ/J4yZ7dUsxsWZEsxNwH5O7F8eEA= -github.com/aws/aws-sdk-go-v2/service/sso v1.27.0/go.mod h1:M0xdEPQtgpNT7kdAX4/vOAPkFj60hSQRb7TvW9B0iug= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.32.0 h1:ywQF2N4VjqX+Psw+jLjMmUL2g1RDHlvri3NxHA08MGI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.32.0/go.mod h1:Z+qv5Q6b7sWiclvbJyPSOT1BRVU9wfSUPaqQzZ1Xg3E= -github.com/aws/aws-sdk-go-v2/service/sts v1.36.0 h1:bRP/a9llXSSgDPk7Rqn5GD/DQCGo6uk95plBFKoXt2M= -github.com/aws/aws-sdk-go-v2/service/sts v1.36.0/go.mod h1:tgBsFzxwl65BWkuJ/x2EUs59bD4SfYKgikvFDJi1S58= +github.com/aws/aws-sdk-go-v2/service/redshift v1.57.0 h1:gFNE53MstNSex5n2AeuqDeO9y6YrAEq5r9ohIo0Q1S4= +github.com/aws/aws-sdk-go-v2/service/redshift v1.57.0/go.mod h1:royODzFrVBRoek5vd76xF7WnwhMGjDj9ZdYcg7Hj8Es= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.36.0 h1:m900Kua81M38+2mViQR0WyXuVJHXfHhBMZE2KEOKCxg= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.36.0/go.mod h1:fKbyyPpRvNxxd3FC8IllvIVic0l4C/IGKIcU7lb5tfI= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 h1:Mc/MKBf2m4VynyJkABoVEN+QzkfLqGj0aiJuEe7cMeM= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.0/go.mod h1:iS5OmxEcN4QIPXARGhavH7S8kETNL11kym6jhoS7IUQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 h1:6csaS/aJmqZQbKhi1EyEMM7yBW653Wy/B9hnBofW+sw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0/go.mod h1:59qHWaY5B+Rs7HGTuVGaC32m0rdpQ68N8QCN3khYiqs= +github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinxeTi2r3+SnmLQfSA= +github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8= github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= @@ -57,6 +61,7 @@ github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= @@ -69,6 +74,8 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -79,6 +86,7 @@ github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= @@ -116,6 +124,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -127,6 +137,8 @@ github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+O github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I= @@ -137,16 +149,22 @@ github.com/hashicorp/terraform-plugin-docs v0.22.0 h1:fwIDStbFel1PPNkM+mDPnpB4ef github.com/hashicorp/terraform-plugin-docs v0.22.0/go.mod h1:55DJVyZ7BNK4t/lANcQ1YpemRuS6KsvIO1BbGA+xzGE= github.com/hashicorp/terraform-plugin-go v0.27.0 h1:ujykws/fWIdsi6oTUT5Or4ukvEan4aN9lY+LOxVP8EE= github.com/hashicorp/terraform-plugin-go v0.27.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o= +github.com/hashicorp/terraform-plugin-go v0.28.0 h1:zJmu2UDwhVN0J+J20RE5huiF3XXlTYVIleaevHZgKPA= +github.com/hashicorp/terraform-plugin-go v0.28.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM= github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA= github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M= github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg= +github.com/hashicorp/terraform-registry-address v0.3.0 h1:HMpK3nqaGFPS9VmgRXrJL/dzHNdheGVKk5k7VlFxzCo= +github.com/hashicorp/terraform-registry-address v0.3.0/go.mod h1:jRGCMiLaY9zii3GLC7hqpSnwhfnCN5yzvY0hh4iCGbM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -156,6 +174,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -184,6 +203,8 @@ github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJ github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -191,8 +212,12 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mmichaelb/redshift-data-sql-driver v0.0.3 h1:xOEYPczRHpbwduJSfjk/EqKCiSkw2nk1AciHsRFzYLk= github.com/mmichaelb/redshift-data-sql-driver v0.0.3/go.mod h1:0g7EsDHiEl9MQd7D6eOJOlruhuV6ie/9XxprwdOpchU= +github.com/mmichaelb/redshift-data-sql-driver v0.4.0 h1:5vl8KRQqJTm2WsAgjF/0zUL7eMmNce3YtU3F6P6CMWc= +github.com/mmichaelb/redshift-data-sql-driver v0.4.0/go.mod h1:D1e55nmxuOialwOsqTruJ3EaKCfCL/zopZGj5uGMv/8= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -242,24 +267,33 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -267,6 +301,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -286,11 +322,14 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -298,11 +337,15 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -310,12 +353,18 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From cd69544b68c62018af7ce24ebf8cc78b2f5c3c21 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 18 Aug 2025 11:53:04 +0200 Subject: [PATCH 17/34] update readme and include limitations --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fefe4191..dc113ccc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,21 @@ It's published on the [Terraform registry](https://registry.terraform.io/provide ## Requirements - [Terraform](https://www.terraform.io/downloads.html) >= 1.0 - - [Go](https://golang.org/doc/install) 1.21 (to build the provider plugin) + - [Go](https://golang.org/doc/install) 1.24 (to build the provider plugin) + +## Limitations + +Due to limited testing capacities, the following features are not tested/stable yet: + +* External Schemas + * Data Catalog Database + * Hive Database + * RDS Postgres Database + * RDS MySQL Database + * Redshift Database +* Temporary Credentials Cluster Identifier +* Temporary Credentials Assume Role +* Datashares ## Building The Provider From 445e5f544dd4f497350e2532600d434041a4d538 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 18 Aug 2025 11:54:20 +0200 Subject: [PATCH 18/34] update docs --- docs/resources/group.md | 2 ++ docs/resources/schema.md | 2 ++ docs/resources/user.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/docs/resources/group.md b/docs/resources/group.md index 8a8763d8..206ac7fc 100644 --- a/docs/resources/group.md +++ b/docs/resources/group.md @@ -41,6 +41,8 @@ resource "redshift_group" "staff" { Import is supported using the following syntax: +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example: + ```shell # Import group with grosysid: SELECT grosysid FROM pg_group WHERE groname = 'mygroup' diff --git a/docs/resources/schema.md b/docs/resources/schema.md index a179a8c2..7ef8beca 100644 --- a/docs/resources/schema.md +++ b/docs/resources/schema.md @@ -279,6 +279,8 @@ Optional: Import is supported using the following syntax: +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example: + ```shell # Import schema with oid: SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = 'myschema'; diff --git a/docs/resources/user.md b/docs/resources/user.md index 42c9a037..946a7c17 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -50,6 +50,8 @@ resource "redshift_user" "user_with_unrestricted_syslog" { Import is supported using the following syntax: +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example: + ```shell # Import user with usesysid: SELECT usesysid FROM pg_user_info WHERE usename = 'mememe' From 694cc204d27ffcc32bfc84312bbd389f9f50f6ba Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 18 Aug 2025 12:28:26 +0200 Subject: [PATCH 19/34] exclude glue data catalog external db from limitations list --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index dc113ccc..076bcbea 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,8 @@ It's published on the [Terraform registry](https://registry.terraform.io/provide ## Limitations Due to limited testing capacities, the following features are not tested/stable yet: - + * External Schemas - * Data Catalog Database * Hive Database * RDS Postgres Database * RDS MySQL Database From 6c9b414ef5dd716c0718e32710251250c9d4048d Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 18 Aug 2025 12:45:29 +0200 Subject: [PATCH 20/34] add missing sql import --- redshift/resource_redshift_database.go | 1 + 1 file changed, 1 insertion(+) diff --git a/redshift/resource_redshift_database.go b/redshift/resource_redshift_database.go index 565442f4..b40692c3 100644 --- a/redshift/resource_redshift_database.go +++ b/redshift/resource_redshift_database.go @@ -1,6 +1,7 @@ package redshift import ( + "database/sql" "fmt" "log" "strconv" From 0090313c870e406dcaedcc93fa52fba71dcbc4f7 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Wed, 20 Aug 2025 17:39:11 +0200 Subject: [PATCH 21/34] remove usage of pq#Array --- redshift/data_source_redshift_group.go | 27 +++++++++++++++++++++++--- redshift/resource_redshift_group.go | 23 ++++++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/redshift/data_source_redshift_group.go b/redshift/data_source_redshift_group.go index 170c6dbf..7f5cab2c 100644 --- a/redshift/data_source_redshift_group.go +++ b/redshift/data_source_redshift_group.go @@ -1,12 +1,12 @@ package redshift import ( + "fmt" "regexp" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/lib/pq" ) func dataSourceRedshiftGroup() *schema.Resource { @@ -43,10 +43,31 @@ func dataSourceRedshiftGroupRead(db *DBConnection, d *schema.ResourceData) error groupUsers []string ) - sql := `SELECT ARRAY(SELECT u.usename FROM pg_user_info u, pg_group g WHERE g.groname = $1 AND u.usesysid = ANY(g.grolist)) AS members, grosysid FROM pg_group WHERE groname = $1` - if err := db.QueryRow(sql, d.Get(groupNameAttr).(string)).Scan(pq.Array(&groupUsers), &groupId); err != nil { + groupName := d.Get(groupNameAttr).(string) + + query := `SELECT u.usename, g.grosysid FROM pg_user_info u, pg_group g WHERE g.groname = $1 AND u.usesysid = ANY(g.grolist);` + rows, err := db.Query(query, groupName) + if err != nil { return err } + defer rows.Close() + for rows.Next() { + if err = rows.Err(); err != nil { + return fmt.Errorf("could not read group members for group name %q: %w", groupName, err) + } + var userName string + if err := rows.Scan(&userName, &groupId); err != nil { + return fmt.Errorf("could not read group members for group name %q: %w", groupName, err) + } + groupUsers = append(groupUsers, userName) + } + if len(groupUsers) == 0 { + // no users found so the group id could not be fetched, we have to query for the name + query = `SELECT grosysid FROM pg_group WHERE groname = $1;` + if err := db.QueryRow(query, groupName).Scan(&groupId); err != nil { + return err + } + } d.SetId(groupId) d.Set(groupUsersAttr, groupUsers) diff --git a/redshift/resource_redshift_group.go b/redshift/resource_redshift_group.go index 9cf70df1..cf5d7eb1 100644 --- a/redshift/resource_redshift_group.go +++ b/redshift/resource_redshift_group.go @@ -64,10 +64,29 @@ func resourceRedshiftGroupReadImpl(db *DBConnection, d *schema.ResourceData) err groupUsers []string ) - query := `SELECT ARRAY(SELECT u.usename FROM pg_user_info u, pg_group g WHERE g.grosysid = $1 AND u.usesysid = ANY(g.grolist)) AS members, groname FROM pg_group WHERE grosysid = $1` - if err := db.QueryRow(query, d.Id()).Scan(pq.Array(&groupUsers), &groupName); err != nil { + query := `SELECT groname, u.usename FROM pg_user_info u, pg_group g WHERE g.grosysid = $1 AND u.usesysid = ANY(g.grolist);` + rows, err := db.Query(query, d.Id()) + if err != nil { return err } + defer rows.Close() + for rows.Next() { + if err = rows.Err(); err != nil { + return fmt.Errorf("could not read group members for group id %q: %w", d.Id(), err) + } + var userName string + if err := rows.Scan(&groupName, &userName); err != nil { + return fmt.Errorf("could not read group members for group id %q: %w", d.Id(), err) + } + groupUsers = append(groupUsers, userName) + } + if len(groupUsers) == 0 { + // no users found so the group name could not be fetched, we have to query for the name + query = `SELECT groname FROM pg_group WHERE grosysid = $1;` + if err := db.QueryRow(query, d.Id()).Scan(&groupName); err != nil { + return err + } + } d.Set(groupNameAttr, groupName) d.Set(groupUsersAttr, groupUsers) From ae3e69b13f83814c1d3c234ae3c1bab0346d71b1 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Wed, 20 Aug 2025 17:40:57 +0200 Subject: [PATCH 22/34] remove db name param from start transaction helper method parameter was unused (always empty) --- redshift/helpers.go | 9 ++------- redshift/resource_redshift_database.go | 4 ++-- redshift/resource_redshift_datashare.go | 8 ++++---- redshift/resource_redshift_default_privileges.go | 6 +++--- redshift/resource_redshift_grant.go | 4 ++-- redshift/resource_redshift_group.go | 6 +++--- redshift/resource_redshift_schema.go | 6 +++--- redshift/resource_redshift_user.go | 6 +++--- 8 files changed, 22 insertions(+), 27 deletions(-) diff --git a/redshift/helpers.go b/redshift/helpers.go index c8c15c0b..fa727735 100644 --- a/redshift/helpers.go +++ b/redshift/helpers.go @@ -26,13 +26,8 @@ const ( pgErrorCodeInsufficientPrivileges = "42501" ) -// startTransaction starts a new DB transaction on the specified database. -// If the database is specified and different from the one configured in the provider, -// it will create a new connection pool if needed. -func startTransaction(client *Client, database string) (*sql.Tx, error) { - if database != "" && database != client.databaseName { - client = client.config.NewClient(database) - } +// startTransaction starts a new DB transaction using the provided client. +func startTransaction(client *Client) (*sql.Tx, error) { db, err := client.Connect() if err != nil { return nil, err diff --git a/redshift/resource_redshift_database.go b/redshift/resource_redshift_database.go index b40692c3..c4a65f07 100644 --- a/redshift/resource_redshift_database.go +++ b/redshift/resource_redshift_database.go @@ -140,7 +140,7 @@ func resourceRedshiftDatabaseCreateFromDatashare(db *DBConnection, d *schema.Res // CREATE DATABASE isn't allowed to run inside a transaction, however ALTER DATABASE // can be - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -251,7 +251,7 @@ WHERE pg_database_info.datid = $1 } func resourceRedshiftDatabaseUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } diff --git a/redshift/resource_redshift_datashare.go b/redshift/resource_redshift_datashare.go index 2a053dcd..0d15506f 100644 --- a/redshift/resource_redshift_datashare.go +++ b/redshift/resource_redshift_datashare.go @@ -97,7 +97,7 @@ such as RA3. } func resourceRedshiftDatashareCreate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -242,7 +242,7 @@ func resourceRedshiftDatashareRead(db *DBConnection, d *schema.ResourceData) err var shareName, owner, producerAccount, producerNamespace, created string var publicAccessible bool - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -313,7 +313,7 @@ func readDatashareSchemas(tx *sql.Tx, shareName string, d *schema.ResourceData) } func resourceRedshiftDatashareUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -405,7 +405,7 @@ func setDatashareSchemas(tx *sql.Tx, d *schema.ResourceData) error { } func resourceRedshiftDatashareDelete(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } diff --git a/redshift/resource_redshift_default_privileges.go b/redshift/resource_redshift_default_privileges.go index 86ac1f7a..03ce0dbf 100644 --- a/redshift/resource_redshift_default_privileges.go +++ b/redshift/resource_redshift_default_privileges.go @@ -99,7 +99,7 @@ func redshiftDefaultPrivileges() *schema.Resource { func resourceRedshiftDefaultPrivilegesDelete(db *DBConnection, d *schema.ResourceData) error { revokeAlterDefaultQuery := createAlterDefaultsRevokeQuery(d) - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -125,7 +125,7 @@ func resourceRedshiftDefaultPrivilegesCreate(db *DBConnection, d *schema.Resourc return fmt.Errorf(`invalid privileges list %+v for object type %q`, privileges, objectType) } - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -162,7 +162,7 @@ func resourceRedshiftDefaultPrivilegesReadImpl(db *DBConnection, d *schema.Resou schemaName, schemaNameSet := d.GetOk(defaultPrivilegesSchemaAttr) ownerName := d.Get(defaultPrivilegesOwnerAttr).(string) - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } diff --git a/redshift/resource_redshift_grant.go b/redshift/resource_redshift_grant.go index f254d4c4..d0d4cf15 100644 --- a/redshift/resource_redshift_grant.go +++ b/redshift/resource_redshift_grant.go @@ -156,7 +156,7 @@ func resourceRedshiftGrantCreate(db *DBConnection, d *schema.ResourceData) error databaseName := getDatabaseName(db, d) - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -180,7 +180,7 @@ func resourceRedshiftGrantCreate(db *DBConnection, d *schema.ResourceData) error } func resourceRedshiftGrantDelete(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } diff --git a/redshift/resource_redshift_group.go b/redshift/resource_redshift_group.go index cf5d7eb1..a3eb4fea 100644 --- a/redshift/resource_redshift_group.go +++ b/redshift/resource_redshift_group.go @@ -97,7 +97,7 @@ func resourceRedshiftGroupReadImpl(db *DBConnection, d *schema.ResourceData) err func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error { groupName := d.Get(groupNameAttr).(string) - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -136,7 +136,7 @@ func resourceRedshiftGroupCreate(db *DBConnection, d *schema.ResourceData) error func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error { groupName := d.Get(groupNameAttr).(string) - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -170,7 +170,7 @@ func resourceRedshiftGroupDelete(db *DBConnection, d *schema.ResourceData) error } func resourceRedshiftGroupUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index 1a2f239e..3e4e05a2 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -567,7 +567,7 @@ func resourceRedshiftSchemaReadExternal(db *DBConnection, d *schema.ResourceData } func resourceRedshiftSchemaDelete(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -588,7 +588,7 @@ func resourceRedshiftSchemaDelete(db *DBConnection, d *schema.ResourceData) erro } func resourceRedshiftSchemaCreate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -780,7 +780,7 @@ func getRedshiftConfigQueryPart(d *schema.ResourceData, sourceDbName string) str } func resourceRedshiftSchemaUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } diff --git a/redshift/resource_redshift_user.go b/redshift/resource_redshift_user.go index 875c037b..2574eb19 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -140,7 +140,7 @@ Amazon Redshift user accounts can only be created and dropped by a database supe } func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -352,7 +352,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error userName := d.Get(userNameAttr).(string) newOwnerName := permanentUsername(db.client.config.Username) - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } @@ -454,7 +454,7 @@ func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error } func resourceRedshiftUserUpdate(db *DBConnection, d *schema.ResourceData) error { - tx, err := startTransaction(db.client, "") + tx, err := startTransaction(db.client) if err != nil { return err } From 89ce2dd9a60df84969399fafd8eea6396cec0972 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Wed, 20 Aug 2025 18:23:46 +0200 Subject: [PATCH 23/34] retrieve username using a sql statement --- redshift/config.go | 27 +++++++++++++++++++++++++++ redshift/resource_redshift_user.go | 6 +++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/redshift/config.go b/redshift/config.go index 44ec5a66..ffe5fee1 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -30,6 +30,9 @@ type Config struct { serverlessCheckMutex *sync.Mutex isServerless bool checkedForServerless bool + + usernameRetrievalMutex *sync.Mutex + retrievedUsername string } // Client struct holding connection string @@ -82,6 +85,30 @@ func (c *Config) IsServerless(db *DBConnection) (bool, error) { return false, err } +func (c *Config) GetUsername(db *DBConnection) (string, error) { + if c.usernameRetrievalMutex == nil { + c.usernameRetrievalMutex = &sync.Mutex{} + } + if c.retrievedUsername != "" { + return c.retrievedUsername, nil + } + c.usernameRetrievalMutex.Lock() + defer c.usernameRetrievalMutex.Unlock() + if c.retrievedUsername != "" { + return c.retrievedUsername, nil + } + row := db.QueryRow("SELECT current_user;") + if row.Err() != nil { + return "", fmt.Errorf("error retrieving current user: %w", row.Err()) + } + var username string + if err := row.Scan(&username); err != nil { + return "", fmt.Errorf("error scanning current user: %w", err) + } + c.retrievedUsername = username + return c.retrievedUsername, nil +} + // 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/resource_redshift_user.go b/redshift/resource_redshift_user.go index 2574eb19..6701b629 100644 --- a/redshift/resource_redshift_user.go +++ b/redshift/resource_redshift_user.go @@ -350,7 +350,11 @@ func validateAndAdjustValidUntil(validUntil string) (string, error) { func resourceRedshiftUserDelete(db *DBConnection, d *schema.ResourceData) error { useSysID := d.Id() userName := d.Get(userNameAttr).(string) - newOwnerName := permanentUsername(db.client.config.Username) + rawUsername, err := db.client.config.GetUsername(db) + if err != nil { + return fmt.Errorf("error retrieving username: %w", err) + } + newOwnerName := permanentUsername(rawUsername) tx, err := startTransaction(db.client) if err != nil { From 3efdef4bc4bdfe775ffbd24088a4215c59e16aba Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Wed, 20 Aug 2025 22:05:26 +0200 Subject: [PATCH 24/34] update config logic to include redshift data driver --- redshift/config.go | 98 ++++-------- redshift/config_data_api.go | 33 ++++ redshift/config_pq_proxy.go | 167 ++++++++++++++++++++ redshift/data_source_redshift_schema.go | 2 +- redshift/provider.go | 200 +++++++++--------------- redshift/provider_test.go | 155 ++++++++++++++++++ redshift/resource_redshift_grant.go | 2 +- redshift/resource_redshift_schema.go | 4 +- 8 files changed, 464 insertions(+), 197 deletions(-) create mode 100644 redshift/config_data_api.go create mode 100644 redshift/config_pq_proxy.go diff --git a/redshift/config.go b/redshift/config.go index ffe5fee1..dd8e412d 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -3,12 +3,8 @@ package redshift import ( "database/sql" "fmt" - "net/url" - "os" - "strings" "sync" - _ "github.com/lib/pq" _ "github.com/mmichaelb/redshift-data-sql-driver" ) @@ -17,15 +13,11 @@ var ( dbRegistry = make(map[string]*DBConnection, 1) ) -// Config - provider config type Config struct { - Host string - Username string - Password string - Port int - Database string - SSLMode string - MaxConns int + DriverName string + ConnStr string + Database string + MaxConns int serverlessCheckMutex *sync.Mutex isServerless bool @@ -35,10 +27,21 @@ type Config struct { retrievedUsername string } +func NewConfig(driverName, connStr, database string, maxConns int) *Config { + return &Config{ + DriverName: driverName, + ConnStr: connStr, + Database: database, + MaxConns: maxConns, + + serverlessCheckMutex: &sync.Mutex{}, + usernameRetrievalMutex: &sync.Mutex{}, + } +} + // Client struct holding connection string type Client struct { - config Config - databaseName string + config Config db *sql.DB } @@ -50,10 +53,9 @@ type DBConnection struct { } // NewClient returns client config for the specified database. -func (c *Config) NewClient(database string) *Client { +func (c *Config) NewClient() *Client { return &Client{ - config: *c, - databaseName: database, + config: *c, } } @@ -86,9 +88,6 @@ func (c *Config) IsServerless(db *DBConnection) (bool, error) { } func (c *Config) GetUsername(db *DBConnection) (string, error) { - if c.usernameRetrievalMutex == nil { - c.usernameRetrievalMutex = &sync.Mutex{} - } if c.retrievedUsername != "" { return c.retrievedUsername, nil } @@ -116,28 +115,19 @@ func (c *Client) Connect() (*DBConnection, error) { dbRegistryLock.Lock() defer dbRegistryLock.Unlock() - dsn := c.config.connStr(c.databaseName) + dsn := c.config.ConnStr + driverName := c.config.DriverName conn, found := dbRegistry[dsn] - if !found { - // todo: put values into config struct - workgroupName, ok := os.LookupEnv("SERVERLESS_WORKGROUP_NAME") - var db *sql.DB - var err error - if !ok { - db, err = sql.Open(proxyDriverName, dsn) - if err != nil { - return nil, fmt.Errorf("error connecting to Redshift server %q: %w", c.config.Host, err) - } - } else { - db, err = sql.Open("redshift-data", fmt.Sprintf("workgroup(%s)/%s?timeout=1m®ion=eu-central-1&transactionMode=non-transactional&requestMode=blocking", workgroupName, c.config.Database)) - if err != nil { - return nil, fmt.Errorf("error connecting to redshift workgroup %q: %w", workgroupName, err) - } + + if !found || conn.Ping() != nil { + db, err := sql.Open(driverName, dsn) + if err != nil { + return nil, fmt.Errorf("error creating Redshift driver instance (driver: %q, dsn: %q): %w", driverName, dsn, err) } // We don't want to retain connection // So when we connect on a specific database which might be managed by terraform, - // we don't keep opened connection in case of the db has to be dopped in the plan. + // we don't keep opened connection in case of the db has to be dropped in the plan. db.SetMaxIdleConns(0) db.SetMaxOpenConns(c.config.MaxConns) @@ -145,38 +135,16 @@ func (c *Client) Connect() (*DBConnection, error) { db, c, } - dbRegistry[dsn] = conn - } - return conn, nil -} - -func (c *Config) connStr(database string) string { - connStr := fmt.Sprintf( - "postgres://%s:%s@%s:%d/%s?%s", - url.QueryEscape(c.Username), - url.QueryEscape(c.Password), - c.Host, - c.Port, - database, - strings.Join(c.connParams(), "&"), - ) - - return connStr -} - -func (c *Config) connParams() []string { - params := map[string]string{} - - params["sslmode"] = c.SSLMode - params["connect_timeout"] = "180" + _, err = c.config.GetUsername(conn) + if err != nil { + return nil, fmt.Errorf("error retrieving username from Redshift database (driver: %q, dsn: %q): %w", driverName, dsn, err) + } - var paramsArray []string - for key, value := range params { - paramsArray = append(paramsArray, fmt.Sprintf("%s=%s", key, url.QueryEscape(value))) + dbRegistry[dsn] = conn } - return paramsArray + return conn, nil } func (c *Client) Close() { diff --git a/redshift/config_data_api.go b/redshift/config_data_api.go new file mode 100644 index 00000000..9bb58810 --- /dev/null +++ b/redshift/config_data_api.go @@ -0,0 +1,33 @@ +package redshift + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const redshiftDataDriverName = "redshift-data" + +func NewDataApiConfig(workgroupName, database, awsRegion string, maxConns int) *Config { + connStr := buildConnStrFromDataApiConfig(workgroupName, database, awsRegion) + return NewConfig(redshiftDataDriverName, connStr, database, maxConns) +} + +func buildConnStrFromDataApiConfig(workgroupName, database, awsRegion string) string { + return fmt.Sprintf( + "workgroup(%s)/%s?region=%s&transactionMode=non-transactional&requestMode=blocking", + workgroupName, database, awsRegion, + ) +} + +func getConfigFromDataApiResourceData(d *schema.ResourceData, database string) (*Config, error) { + workgroupName := d.Get("data_api.0.workgroup_name").(string) + if workgroupName == "" { + return nil, fmt.Errorf(`attribute "workgroup_name" is required in data_api configuration`) + } + region := d.Get("data_api.0.region").(string) + if region == "" { + return nil, fmt.Errorf(`attribute "region" is required in data_api configuration`) + } + return NewDataApiConfig(workgroupName, database, region, 1), nil +} diff --git a/redshift/config_pq_proxy.go b/redshift/config_pq_proxy.go new file mode 100644 index 00000000..e9ff5a1a --- /dev/null +++ b/redshift/config_pq_proxy.go @@ -0,0 +1,167 @@ +package redshift + +import ( + "context" + "fmt" + "log" + "net/url" + "sort" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/redshift" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + _ "github.com/lib/pq" +) + +type temporaryCredentialsResolverFunc func(username string, d *schema.ResourceData) (string, string, error) + +func NewPqConfig(host, database, username, password string, port int, sslMode string, maxConns int) *Config { + connStr := buildConnStrFromPqConfig(host, database, username, password, port, sslMode, maxConns) + return NewConfig(proxyDriverName, connStr, database, maxConns) +} + +func buildConnStrFromPqConfig(host, database, username, password string, port int, sslMode string, maxConns int) string { + params := map[string]string{} + + params["sslmode"] = sslMode + params["connect_timeout"] = "180" + + var paramsArray []string + for key, value := range params { + paramsArray = append(paramsArray, fmt.Sprintf("%s=%s", key, url.QueryEscape(value))) + } + sort.Strings(paramsArray) + + return fmt.Sprintf( + "postgres://%s:%s@%s:%d/%s?%s", + url.QueryEscape(username), + url.QueryEscape(password), + host, + port, + database, + strings.Join(paramsArray, "&"), + ) +} +func getRequiredResourceDataValue[V int | string](d *schema.ResourceData, path string) (V, error) { + valueRaw, valuePresent := d.GetOk(path) + if !valuePresent { + var emptyValue V + return emptyValue, fmt.Errorf("attribute %q is required in pq configuration", path) + } + return valueRaw.(V), nil +} + +func getConfigFromPqResourceData(d *schema.ResourceData, database string, maxConnections int, temporaryCredentialsResolver temporaryCredentialsResolverFunc) (*Config, error) { + var err error + var host, username, password, sslMode string + var port int + if host, err = getRequiredResourceDataValue[string](d, "host"); err != nil { + return nil, err + } + if username, err = getRequiredResourceDataValue[string](d, "username"); err != nil { + return nil, err + } + if port, err = getRequiredResourceDataValue[int](d, "port"); err != nil { + return nil, err + } + if sslMode, err = getRequiredResourceDataValue[string](d, "sslmode"); err != nil { + return nil, err + } + _, useTemporaryCredentials := d.GetOk("temporary_credentials") + if useTemporaryCredentials { + log.Println("[DEBUG] using temporary credentials authentication") + username, password, err = temporaryCredentialsResolver(username, d) + if err != nil { + return nil, fmt.Errorf("failed to resolve temporary credentials: %w", err) + } + log.Printf("[DEBUG] got temporary credentials with username %s\n", username) + } else { + log.Println("[DEBUG] using password authentication") + if password, err = getRequiredResourceDataValue[string](d, "password"); err != nil { + return nil, err + } + } + return NewPqConfig(host, database, username, password, port, sslMode, maxConnections), nil +} + +// temporaryCredentials gets temporary credentials using GetClusterCredentials +func temporaryCredentials(username string, d *schema.ResourceData) (string, string, error) { + sdkClient, err := redshiftSdkClient(d) + if err != nil { + return "", "", err + } + clusterIdentifier, clusterIdentifierIsSet := d.GetOk("temporary_credentials.0.cluster_identifier") + if !clusterIdentifierIsSet { + return "", "", fmt.Errorf("temporary_credentials not configured") + } + input := &redshift.GetClusterCredentialsInput{ + ClusterIdentifier: aws.String(clusterIdentifier.(string)), + DbName: aws.String(d.Get("database").(string)), + DbUser: aws.String(username), + } + if autoCreateUser, ok := d.GetOk("temporary_credentials.0.auto_create_user"); ok { + input.AutoCreate = aws.Bool(autoCreateUser.(bool)) + } + if dbGroups, ok := d.GetOk("temporary_credentials.0.db_groups"); ok { + if dbGroups != nil { + dbGroupsList := dbGroups.(*schema.Set).List() + if len(dbGroupsList) > 0 { + var groups []string + for _, group := range dbGroupsList { + if group.(string) != "" { + groups = append(groups, group.(string)) + } + } + input.DbGroups = groups + } + } + } + if durationSeconds, ok := d.GetOk("temporary_credentials.0.duration_seconds"); ok { + duration := durationSeconds.(int) + if duration > 0 { + input.DurationSeconds = aws.Int32(int32(duration)) + } + } + log.Println("[DEBUG] making GetClusterCredentials request") + response, err := sdkClient.GetClusterCredentials(context.TODO(), input) + if err != nil { + return "", "", err + } + return aws.ToString(response.DbUser), aws.ToString(response.DbPassword), nil +} + +func redshiftSdkClient(d *schema.ResourceData) (*redshift.Client, error) { + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + return nil, err + } + + if region := d.Get("temporary_credentials.0.region").(string); region != "" { + cfg.Region = region + } + + if _, ok := d.GetOk("temporary_credentials.0.assume_role"); ok { + var parsedRoleArn string + if roleArn, ok := d.GetOk("temporary_credentials.0.assume_role.0.arn"); ok { + parsedRoleArn = roleArn.(string) + } + log.Printf("[DEBUG] Assuming role provided in configuration: [%s]", parsedRoleArn) + opts := func(options *stscreds.AssumeRoleOptions) { + options.Duration = time.Duration(defaultTemporaryCredentialsAssumeRoleDurationInSeconds) * time.Second + if externalID, ok := d.GetOk("temporary_credentials.0.assume_role.0.external_id"); ok { + options.ExternalID = aws.String(externalID.(string)) + } + if sessionName, ok := d.GetOk("temporary_credentials.0.assume_role.0.session_name"); ok { + options.RoleSessionName = sessionName.(string) + } + } + stsClient := sts.NewFromConfig(cfg) + cfg.Credentials = stscreds.NewAssumeRoleProvider(stsClient, parsedRoleArn, opts) + } + return redshift.NewFromConfig(cfg), nil +} diff --git a/redshift/data_source_redshift_schema.go b/redshift/data_source_redshift_schema.go index cbc31a56..c36e4db8 100644 --- a/redshift/data_source_redshift_schema.go +++ b/redshift/data_source_redshift_schema.go @@ -276,7 +276,7 @@ func dataSourceRedshiftSchemaRead(db *DBConnection, d *schema.ResourceData) erro LEFT JOIN pg_user_info ON (svv_all_schemas.database_name = $1 AND pg_user_info.usesysid = svv_all_schemas.schema_owner) WHERE svv_all_schemas.database_name = $1 - AND svv_all_schemas.schema_name = $2`, db.client.databaseName, d.Get(schemaNameAttr).(string)).Scan(&schemaId, &schemaOwner, &schemaType) + AND svv_all_schemas.schema_name = $2`, db.client.config.Database, d.Get(schemaNameAttr).(string)).Scan(&schemaId, &schemaOwner, &schemaType) if err != nil { return err } diff --git a/redshift/provider.go b/redshift/provider.go index 0afe4107..66a1e1c1 100644 --- a/redshift/provider.go +++ b/redshift/provider.go @@ -2,17 +2,11 @@ package redshift import ( "context" - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "log" "regexp" - "time" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/credentials/stscreds" - "github.com/aws/aws-sdk-go-v2/service/redshift" - "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -26,16 +20,18 @@ func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "host": { - Type: schema.TypeString, - Description: "Name of Redshift server address to connect to.", - Required: true, - DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_HOST", ""), + Type: schema.TypeString, + Description: "Name of Redshift server address to connect to.", + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_HOST", ""), + ConflictsWith: []string{"data_api"}, }, "username": { - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_USER", "root"), - Description: "Redshift user name to connect as.", + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_USER", "root"), + Description: "Redshift user name to connect as.", + ConflictsWith: []string{"data_api"}, }, "password": { Type: schema.TypeString, @@ -45,13 +41,15 @@ func Provider() *schema.Provider { Sensitive: true, ConflictsWith: []string{ "temporary_credentials", + "data_api", }, }, "port": { - Type: schema.TypeInt, - Description: "The Redshift port number to connect to at the server host.", - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_PORT", 5439), + Type: schema.TypeInt, + Description: "The Redshift port number to connect to at the server host.", + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_PORT", 5439), + ConflictsWith: []string{"data_api"}, }, "sslmode": { Type: schema.TypeString, @@ -64,6 +62,7 @@ func Provider() *schema.Provider { "verify-ca", "verify-full", }, false), + ConflictsWith: []string{"data_api"}, }, "database": { Type: schema.TypeString, @@ -72,11 +71,48 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_DATABASE", "redshift"), }, "max_connections": { - Type: schema.TypeInt, - Optional: true, - Default: defaultProviderMaxOpenConnections, - Description: "Maximum number of connections to establish to the database. Zero means unlimited.", - ValidateFunc: validation.IntAtLeast(-1), + Type: schema.TypeInt, + Optional: true, + Default: defaultProviderMaxOpenConnections, + Description: "Maximum number of connections to establish to the database. Zero means unlimited.", + ValidateFunc: validation.IntAtLeast(-1), + ConflictsWith: []string{"data_api"}, + }, + "data_api": { + Type: schema.TypeList, + Optional: true, + Description: "Configuration for using the Redshift Data API. This can only be used for serverless Redshift clusters.", + MaxItems: 1, + ConflictsWith: []string{ + "host", + "username", + "password", + "port", + "sslmode", + "max_connections", + "temporary_credentials", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "workgroup_name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the Redshift Serverless workgroup to connect to.", + DefaultFunc: schema.EnvDefaultFunc("REDSHIFT_DATA_API_SERVERLESS_WORKGROUP_NAME", nil), + // https://docs.aws.amazon.com/redshift-serverless/latest/APIReference/API_Workgroup.html#:~:text=Required%3A%20No-,workgroupName,-The%20name%20of + ValidateFunc: validation.All( + validation.StringLenBetween(3, 64), + validation.StringMatch(regexp.MustCompile("[a-z0-9-]+"), "must be lowercase alphanumeric or hyphen characters"), + ), + }, + "region": { + Type: schema.TypeString, + Required: true, + Description: "The AWS region where the Redshift Serverless workgroup is located. If not specified, the region will be determined from the AWS SDK configuration.", + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"AWS_REGION", "AWS_DEFAULT_REGION"}, nil), + }, + }, + }, }, "temporary_credentials": { Type: schema.TypeList, @@ -85,6 +121,7 @@ func Provider() *schema.Provider { MaxItems: 1, ConflictsWith: []string{ "password", + "data_api", }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -149,118 +186,25 @@ func Provider() *schema.Provider { } func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { - username, password, err := resolveCredentials(d) + cfg, err := getConfigFromResourceData(d, temporaryCredentials) if err != nil { return nil, diag.FromErr(err) } - cfg := Config{ - Host: d.Get("host").(string), - Port: d.Get("port").(int), - Username: username, - Password: password, - Database: d.Get("database").(string), - SSLMode: d.Get("sslmode").(string), - MaxConns: d.Get("max_connections").(int), - } log.Println("[DEBUG] creating database client") - client := cfg.NewClient(d.Get("database").(string)) + client := cfg.NewClient() log.Println("[DEBUG] created database client") return client, nil } -func resolveCredentials(d *schema.ResourceData) (string, string, error) { - username, ok := d.GetOk("username") - if (!ok) || username == nil { - return "", "", fmt.Errorf("username is required") - } - if _, useTemporaryCredentials := d.GetOk("temporary_credentials"); useTemporaryCredentials { - log.Println("[DEBUG] using temporary credentials authentication") - dbUser, dbPassword, err := temporaryCredentials(username.(string), d) - log.Printf("[DEBUG] got temporary credentials with username %s\n", dbUser) - return dbUser, dbPassword, err - } - - password, _ := d.GetOk("password") - log.Println("[DEBUG] using password authentication") - return username.(string), password.(string), nil -} - -// temporaryCredentials gets temporary credentials using GetClusterCredentials -func temporaryCredentials(username string, d *schema.ResourceData) (string, string, error) { - sdkClient, err := redshiftSdkClient(d) - if err != nil { - return "", "", err - } - clusterIdentifier, clusterIdentifierIsSet := d.GetOk("temporary_credentials.0.cluster_identifier") - if !clusterIdentifierIsSet { - return "", "", fmt.Errorf("temporary_credentials not configured") - } - input := &redshift.GetClusterCredentialsInput{ - ClusterIdentifier: aws.String(clusterIdentifier.(string)), - DbName: aws.String(d.Get("database").(string)), - DbUser: aws.String(username), - } - if autoCreateUser, ok := d.GetOk("temporary_credentials.0.auto_create_user"); ok { - input.AutoCreate = aws.Bool(autoCreateUser.(bool)) - } - if dbGroups, ok := d.GetOk("temporary_credentials.0.db_groups"); ok { - if dbGroups != nil { - dbGroupsList := dbGroups.(*schema.Set).List() - if len(dbGroupsList) > 0 { - var groups []string - for _, group := range dbGroupsList { - if group.(string) != "" { - groups = append(groups, group.(string)) - } - } - input.DbGroups = groups - } - } - } - if durationSeconds, ok := d.GetOk("temporary_credentials.0.duration_seconds"); ok { - duration := durationSeconds.(int) - if duration > 0 { - input.DurationSeconds = aws.Int32(int32(duration)) - } - } - log.Println("[DEBUG] making GetClusterCredentials request") - response, err := sdkClient.GetClusterCredentials(context.TODO(), input) - if err != nil { - return "", "", err - } - return aws.ToString(response.DbUser), aws.ToString(response.DbPassword), nil -} - -func redshiftSdkClient(d *schema.ResourceData) (*redshift.Client, error) { - cfg, err := config.LoadDefaultConfig(context.TODO()) - if err != nil { - return nil, err - } - - if region := d.Get("temporary_credentials.0.region").(string); region != "" { - cfg.Region = region - } - - if _, ok := d.GetOk("temporary_credentials.0.assume_role"); ok { - var parsedRoleArn string - if roleArn, ok := d.GetOk("temporary_credentials.0.assume_role.0.arn"); ok { - parsedRoleArn = roleArn.(string) - } - log.Printf("[DEBUG] Assuming role provided in configuration: [%s]", parsedRoleArn) - opts := func(options *stscreds.AssumeRoleOptions) { - options.Duration = time.Duration(defaultTemporaryCredentialsAssumeRoleDurationInSeconds) * time.Second - if externalID, ok := d.GetOk("temporary_credentials.0.assume_role.0.external_id"); ok { - options.ExternalID = aws.String(externalID.(string)) - } - if sessionName, ok := d.GetOk("temporary_credentials.0.assume_role.0.session_name"); ok { - options.RoleSessionName = sessionName.(string) - } - } - stsClient := sts.NewFromConfig(cfg) - cfg.Credentials = stscreds.NewAssumeRoleProvider(stsClient, parsedRoleArn, opts) - } - return redshift.NewFromConfig(cfg), nil +func getConfigFromResourceData(d *schema.ResourceData, temporaryCredentialsResolver temporaryCredentialsResolverFunc) (*Config, error) { + database := d.Get("database").(string) + maxConnections := d.Get("max_connections").(int) + _, useDataApi := d.GetOk("data_api.0") + if useDataApi { + return getConfigFromDataApiResourceData(d, database) + } + return getConfigFromPqResourceData(d, database, maxConnections, temporaryCredentialsResolver) } func assumeRoleSchema() *schema.Schema { diff --git a/redshift/provider_test.go b/redshift/provider_test.go index ab4fbe7a..a519d53c 100644 --- a/redshift/provider_test.go +++ b/redshift/provider_test.go @@ -2,6 +2,7 @@ package redshift import ( "context" + "fmt" "os" "strings" "testing" @@ -132,3 +133,157 @@ func prepareRedshiftTemporaryCredentialsTestCases(t *testing.T, provider *schema os.Setenv("REDSHIFT_USER", username) initTemporaryCredentialsProvider(t, provider) } + +func Test_getConfigFromResourceData(t *testing.T) { + defer unsetAndSetEnvVars("AWS_REGION", "AWS_DEFAULT_REGION")() + type args struct { + d *schema.ResourceData + } + const tempUsername, tempPassword = "temp-user", "temp-password" + fakeTemporaryCredentialsResolver := func(username string, d *schema.ResourceData) (string, string, error) { + return tempUsername, tempPassword, nil + } + tests := []struct { + name string + args args + want *Config + wantErr bool + }{ + { + "Data API config", + args{ + d: schema.TestResourceDataRaw(t, Provider().Schema, map[string]interface{}{ + "database": "some-database", + "data_api": []interface{}{ + map[string]interface{}{ + "workgroup_name": "some-workgroup", + "region": "us-west-2", + }, + }, + }), + }, + &Config{ + DriverName: redshiftDataDriverName, + ConnStr: "workgroup(some-workgroup)/some-database?region=us-west-2&transactionMode=non-transactional&requestMode=blocking", + Database: "some-database", + MaxConns: 1, + }, + false, + }, + { + "Data API config missing region", + args{ + d: schema.TestResourceDataRaw(t, Provider().Schema, map[string]interface{}{ + "database": "some-database", + "data_api": []interface{}{ + map[string]interface{}{ + "workgroup_name": "some-workgroup", + }, + }, + }), + }, + nil, + true, + }, + { + "PQ config", + args{ + d: schema.TestResourceDataRaw(t, Provider().Schema, map[string]interface{}{ + "username": "some-user", + "password": "some-pw", + "host": "some-host", + "port": 4122, + "database": "some-database", + "sslmode": "require", + "max_connections": 10, + }), + }, + &Config{ + DriverName: "postgresql-proxy", + ConnStr: "postgres://some-user:some-pw@some-host:4122/some-database?connect_timeout=180&sslmode=require", + Database: "some-database", + MaxConns: 10, + }, + false, + }, + { + "PQ config - fake temporary credentials", + args{ + d: schema.TestResourceDataRaw(t, Provider().Schema, map[string]interface{}{ + "username": "some-user", + "host": "some-host", + "port": 4122, + "database": "some-database", + "sslmode": "require", + "temporary_credentials": []interface{}{ + map[string]interface{}{ + "cluster_identifier": "some-cluster", + }, + }, + }), + }, + &Config{ + DriverName: "postgresql-proxy", + ConnStr: fmt.Sprintf("postgres://%s:%s@some-host:4122/some-database?connect_timeout=180&sslmode=require", tempUsername, tempPassword), + Database: "some-database", + MaxConns: 20, + }, + false, + }, + { + "PQ config - missing host", + args{ + d: schema.TestResourceDataRaw(t, Provider().Schema, map[string]interface{}{ + "username": "some-user", + "port": 4122, + "database": "some-database", + "sslmode": "require", + }), + }, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getConfigFromResourceData(tt.args.d, fakeTemporaryCredentialsResolver) + if (err != nil) != tt.wantErr { + t.Errorf("getConfigFromResourceData() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr { + return + } + if tt.want.ConnStr != got.ConnStr { + t.Errorf("getConfigFromResourceData() ConnStr = %q, want %q", got.ConnStr, tt.want.ConnStr) + } + if tt.want.DriverName != got.DriverName { + t.Errorf("getConfigFromResourceData() DriverName = %q, want %q", got.DriverName, tt.want.DriverName) + } + if tt.want.MaxConns != got.MaxConns { + t.Errorf("getConfigFromResourceData() MaxConns = %d, want %d", got.MaxConns, tt.want.MaxConns) + } + if tt.want.Database != got.Database { + t.Errorf("getConfigFromResourceData() Database = %d, want %d", got.MaxConns, tt.want.MaxConns) + } + }) + } +} + +func unsetAndSetEnvVars(envName ...string) func() { + envValues := make(map[string]string) + for _, env := range envName { + envValue := os.Getenv(env) + if envValue != "" { + envValues[env] = envValue + os.Unsetenv(env) + } + } + return func() { + for key, value := range envValues { + if err := os.Setenv(key, value); err != nil { + fmt.Printf("Failed to set environment variable %s: %v\n", key, err) + } + } + } +} diff --git a/redshift/resource_redshift_grant.go b/redshift/resource_redshift_grant.go index d0d4cf15..edc94d8e 100644 --- a/redshift/resource_redshift_grant.go +++ b/redshift/resource_redshift_grant.go @@ -842,7 +842,7 @@ func createGrantsQuery(d *schema.ResourceData, databaseName string) string { } func getDatabaseName(db *DBConnection, d *schema.ResourceData) string { - databaseName := db.client.databaseName + databaseName := db.client.config.Database if database, ok := d.GetOk(grantDatabaseAttr); ok { databaseName = database.(string) } diff --git a/redshift/resource_redshift_schema.go b/redshift/resource_redshift_schema.go index 3e4e05a2..bf1e6fd4 100644 --- a/redshift/resource_redshift_schema.go +++ b/redshift/resource_redshift_schema.go @@ -418,7 +418,7 @@ func resourceRedshiftSchemaReadImpl(db *DBConnection, d *schema.ResourceData) er LEFT JOIN pg_user_info ON (svv_all_schemas.database_name = $1 AND pg_user_info.usesysid = svv_all_schemas.schema_owner) WHERE svv_all_schemas.database_name = $1 - AND pg_namespace.oid = $2`, db.client.databaseName, d.Id()).Scan(&schemaName, &schemaOwner, &schemaType) + AND pg_namespace.oid = $2`, db.client.config.Database, d.Id()).Scan(&schemaName, &schemaOwner, &schemaType) if err != nil { return err } @@ -447,7 +447,7 @@ func resourceRedshiftSchemaReadLocal(db *DBConnection, d *schema.ResourceData) e FROM svv_redshift_schema_quota WHERE database_name = $1 AND schema_name = $2 - `, db.client.databaseName, d.Get(schemaNameAttr)).Scan(&schemaQuota) + `, db.client.config.Database, d.Get(schemaNameAttr)).Scan(&schemaQuota) } else { err = db.QueryRow(` SELECT From 1d725caa473d275cf221e655f415e87825f4db14 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Wed, 20 Aug 2025 22:12:19 +0200 Subject: [PATCH 25/34] add test for redshift data api connection --- redshift/provider_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/redshift/provider_test.go b/redshift/provider_test.go index a519d53c..debe2d79 100644 --- a/redshift/provider_test.go +++ b/redshift/provider_test.go @@ -123,6 +123,22 @@ func TestAccRedshiftTemporaryCredentialsAssumeRole(t *testing.T) { defer db.Close() } +func TestAccRedshiftDataApiServerlessConnect(t *testing.T) { + _ = getEnvOrSkip("REDSHIFT_DATA_API_SERVERLESS_WORKGROUP_NAME", t) + defer unsetAndSetEnvVars("REDSHIFT_HOST")() + provider := Provider() + provider.Configure(context.Background(), terraform.NewResourceConfigRaw(map[string]interface{}{})) + client, ok := provider.Meta().(*Client) + if !ok { + t.Fatal("Unable to initialize client") + } + db, err := client.Connect() + if err != nil { + t.Fatalf("Unable to connect to database: %s", err) + } + defer db.Close() +} + func prepareRedshiftTemporaryCredentialsTestCases(t *testing.T, provider *schema.Provider) { redshiftPassword := os.Getenv("REDSHIFT_PASSWORD") defer os.Setenv("REDSHIFT_PASSWORD", redshiftPassword) From 393b59739ddd69ac100b2e206ca94caac6f1407c Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 21 Aug 2025 12:59:31 +0200 Subject: [PATCH 26/34] add missing rows#Close statement to serverless detection func --- redshift/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/redshift/config.go b/redshift/config.go index dd8e412d..7b017001 100644 --- a/redshift/config.go +++ b/redshift/config.go @@ -71,9 +71,10 @@ func (c *Config) IsServerless(db *DBConnection) (bool, error) { c.checkedForServerless = true - _, err := db.Query("SELECT 1 FROM SYS_SERVERLESS_USAGE") + rows, 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 { + defer rows.Close() c.isServerless = true return true, nil } From 48d6202fb747b1a5ebea8bf8f12b9755ab10eedc Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 21 Aug 2025 12:59:49 +0200 Subject: [PATCH 27/34] add option to enable redshift data sql provider debug --- redshift/provider_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/redshift/provider_test.go b/redshift/provider_test.go index debe2d79..b04b6e83 100644 --- a/redshift/provider_test.go +++ b/redshift/provider_test.go @@ -3,6 +3,7 @@ package redshift import ( "context" "fmt" + "log" "os" "strings" "testing" @@ -11,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + redshiftdatasqldriver "github.com/mmichaelb/redshift-data-sql-driver" ) var ( @@ -43,6 +45,9 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("REDSHIFT_USER"); v == "" { t.Fatal("REDSHIFT_USER must be set for acceptance tests") } + if v := os.Getenv("REDSHIFT_TEST_ACC_DEBUG_REDSHIFT_DATA"); v != "" { + redshiftdatasqldriver.SetDebugLogger(log.New(os.Stdout, "[redshift-data][debug]", log.Ldate|log.Ltime|log.Lshortfile)) + } } func initTemporaryCredentialsProvider(t *testing.T, provider *schema.Provider) { From 44c4084a512a16c4d2c05c1783cdcb8111669e37 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 21 Aug 2025 13:00:41 +0200 Subject: [PATCH 28/34] separate user update and login tests --- redshift/resource_redshift_user_test.go | 176 +++++++++++++----------- 1 file changed, 94 insertions(+), 82 deletions(-) diff --git a/redshift/resource_redshift_user_test.go b/redshift/resource_redshift_user_test.go index 665674fd..2f63569d 100644 --- a/redshift/resource_redshift_user_test.go +++ b/redshift/resource_redshift_user_test.go @@ -19,24 +19,109 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestAccRedshiftUser_Basic(t *testing.T) { +const testAccRedshiftUserLoginConfig = ` +resource "redshift_user" "with_email" { + name = "John-and-Jane.doe@example.com" + password = "Foobarbaz1" +} + +resource "redshift_user" "with_hashed_password" { + name = "hashed_password" + password = "Foobarbaz3" +} +` + +const testAccRedshiftUserLoginUpdateConfig = ` +resource "redshift_user" "with_email" { + name = "John-and-Jane.doe@example.com" + password = "Foobarbaz1" +} + +resource "redshift_user" "with_hashed_password" { + name = "hashed_password" + password = "md5ad3b897bab2474bc7e408326cb18c42f" +} +` + +func TestAccRedshiftUser_Login(t *testing.T) { + if os.Getenv("REDSHIFT_TEST_ACC_SKIP_USER_LOGIN") != "" { + t.Skipf("Skipping user login test as REDSHIFT_TEST_ACC_SKIP_USER_LOGIN is set") + } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviders, CheckDestroy: testAccCheckRedshiftUserDestroy, Steps: []resource.TestStep{ { - Config: testAccRedshiftUserConfig, + Config: testAccRedshiftUserLoginConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckRedshiftUserExists("user_simple"), - resource.TestCheckResourceAttr("redshift_user.simple", "name", "user_simple"), - testAccCheckRedshiftUserExists("John-and-Jane.doe@example.com"), resource.TestCheckResourceAttr("redshift_user.with_email", "name", "John-and-Jane.doe@example.com"), testAccCheckRedshiftUserCanLogin("John-and-Jane.doe@example.com", "Foobarbaz1"), testAccCheckRedshiftUserExists("hashed_password"), - testAccCheckRedshiftUserCanLogin("hashed_password", "Foobarbaz2"), + testAccCheckRedshiftUserCanLogin("hashed_password", "Foobarbaz3"), + ), + }, + { + Config: testAccRedshiftUserLoginUpdateConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRedshiftUserExists("hashed_password"), + testAccCheckRedshiftUserCanLogin("hashed_password", "Foobarbaz6"), + ), + }, + }, + }) +} + +const testAccRedshiftUserConfig = ` +resource "redshift_user" "simple" { + name = "user_simple" +} + +resource "redshift_user" "user_with_defaults" { + name = "user_defaults" + valid_until = "infinity" + superuser = false + create_database = false + connection_limit = -1 + password = "" +} + +resource "redshift_user" "user_with_create_database" { + name = "user_create_database" + create_database = true +} + +resource "redshift_user" "user_with_unrestricted_syslog" { + name = "user_syslog" + syslog_access = "UNRESTRICTED" +} + +resource "redshift_user" "user_superuser" { + name = "user_superuser" + superuser = true + password = "FooBarBaz123" +} + +resource "redshift_user" "user_timeout" { + name = "user_timeout" + password = "FooBarBaz123" + session_timeout = 60 +} +` + +func TestAccRedshiftUser_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckRedshiftUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRedshiftUserConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRedshiftUserExists("user_simple"), + resource.TestCheckResourceAttr("redshift_user.simple", "name", "user_simple"), testAccCheckRedshiftUserExists("user_defaults"), resource.TestCheckResourceAttr("redshift_user.user_with_defaults", "name", "user_defaults"), @@ -74,7 +159,6 @@ func TestAccRedshiftUser_Update(t *testing.T) { var configCreate = ` resource "redshift_user" "update_user" { name = "update_user" - password = "Foobarbaz1" valid_until = "2038-01-04 12:00:00+00" } ` @@ -83,16 +167,6 @@ resource "redshift_user" "update_user" { resource "redshift_user" "update_user" { name = "update_user2" connection_limit = 5 - password = "Foobarbaz5" - syslog_access = "UNRESTRICTED" - create_database = true -} -` - var configUpdate2 = ` -resource "redshift_user" "update_user" { - name = "update_user2" - connection_limit = 5 - password = "md508d5d11f1f947091b312fb36b25e621f" syslog_access = "UNRESTRICTED" create_database = true } @@ -128,14 +202,6 @@ resource "redshift_user" "update_user" { resource.TestCheckResourceAttr("redshift_user.update_user", "create_database", "true"), ), }, - { - Config: configUpdate2, - Check: resource.ComposeTestCheckFunc( - testAccCheckRedshiftUserExists("update_user2"), - testAccCheckRedshiftUserCanLogin("update_user2", "Foobarbaz6"), - resource.TestCheckResourceAttr("redshift_user.update_user", "password", "md508d5d11f1f947091b312fb36b25e621f"), - ), - }, // apply the first one again to check if all parameters roll back properly { Config: configCreate, @@ -427,53 +493,6 @@ func checkUserExists(client *Client, user string) (bool, error) { return true, nil } -const testAccRedshiftUserConfig = ` -resource "redshift_user" "simple" { - name = "user_simple" -} - -resource "redshift_user" "with_email" { - name = "John-and-Jane.doe@example.com" - password = "Foobarbaz1" -} - -resource "redshift_user" "with_hashed_password" { - name = "hashed_password" - password = "md5ad3b897bab2474bc7e408326cb18c42f" -} - -resource "redshift_user" "user_with_defaults" { - name = "user_defaults" - valid_until = "infinity" - superuser = false - create_database = false - connection_limit = -1 - password = "" -} - -resource "redshift_user" "user_with_create_database" { - name = "user_create_database" - create_database = true -} - -resource "redshift_user" "user_with_unrestricted_syslog" { - name = "user_syslog" - syslog_access = "UNRESTRICTED" -} - -resource "redshift_user" "user_superuser" { - name = "user_superuser" - superuser = true - password = "FooBarBaz123" -} - -resource "redshift_user" "user_timeout" { - name = "user_timeout" - password = "FooBarBaz123" - session_timeout = 60 -} -` - func TestPermanentUsername(t *testing.T) { expected := "user" if result := permanentUsername(expected); result != expected { @@ -508,17 +527,10 @@ func testAccCheckRedshiftUserCanLogin(user string, password string) resource.Tes if !ok { sslMode = "require" } - config := &Config{ - Host: os.Getenv("REDSHIFT_HOST"), - Port: portNum, - Username: user, - Password: password, - Database: database, - SSLMode: sslMode, - MaxConns: defaultProviderMaxOpenConnections, - } + config := NewPqConfig(os.Getenv("REDSHIFT_HOST"), database, user, password, portNum, sslMode, + defaultProviderMaxOpenConnections) - client := config.NewClient(database) + client := config.NewClient() if err != nil { return fmt.Errorf("user is unable to login: %w", err) } From 8b03b5348400876903e26a14b10a4298e1761b81 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 21 Aug 2025 13:01:21 +0200 Subject: [PATCH 29/34] restructure/reformat code --- redshift/resource_redshift_grant.go | 4 ++-- redshift/resource_redshift_group_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/redshift/resource_redshift_grant.go b/redshift/resource_redshift_grant.go index edc94d8e..36871d4f 100644 --- a/redshift/resource_redshift_grant.go +++ b/redshift/resource_redshift_grant.go @@ -544,6 +544,7 @@ func readCallableGrants(db *DBConnection, d *schema.ResourceData) error { if err != nil { return err } + defer rows.Close() contains := func(callables []string, objName string) bool { for _, callable := range callables { @@ -553,7 +554,6 @@ func readCallableGrants(db *DBConnection, d *schema.ResourceData) error { } return false } - defer rows.Close() privilegesSet := schema.NewSet(schema.HashString, nil) for rows.Next() { @@ -626,9 +626,9 @@ func readLanguageGrants(db *DBConnection, d *schema.ResourceData) error { if err != nil { return err } + defer rows.Close() objects := d.Get(grantObjectsAttr).(*schema.Set) - defer rows.Close() for rows.Next() { var objName string diff --git a/redshift/resource_redshift_group_test.go b/redshift/resource_redshift_group_test.go index 9df44a52..22cada16 100644 --- a/redshift/resource_redshift_group_test.go +++ b/redshift/resource_redshift_group_test.go @@ -74,8 +74,8 @@ func TestAccRedshiftGroup_Update(t *testing.T) { } resource "redshift_user" "group_update_user3" { - name = %[3]q - } + name = %[3]q + } resource "redshift_group" "update_group" { name = %[4]q From 9e31f87c713ffe432c62872e006e4f8a25ba3a36 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Thu, 21 Aug 2025 14:12:19 +0200 Subject: [PATCH 30/34] fix user login/update test --- redshift/resource_redshift_user_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/redshift/resource_redshift_user_test.go b/redshift/resource_redshift_user_test.go index 2f63569d..170b7a04 100644 --- a/redshift/resource_redshift_user_test.go +++ b/redshift/resource_redshift_user_test.go @@ -57,9 +57,11 @@ func TestAccRedshiftUser_Login(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRedshiftUserExists("John-and-Jane.doe@example.com"), resource.TestCheckResourceAttr("redshift_user.with_email", "name", "John-and-Jane.doe@example.com"), + resource.TestCheckResourceAttr("redshift_user.with_email", "password", "Foobarbaz1"), testAccCheckRedshiftUserCanLogin("John-and-Jane.doe@example.com", "Foobarbaz1"), testAccCheckRedshiftUserExists("hashed_password"), + resource.TestCheckResourceAttr("redshift_user.with_hashed_password", "password", "Foobarbaz3"), testAccCheckRedshiftUserCanLogin("hashed_password", "Foobarbaz3"), ), }, @@ -67,6 +69,7 @@ func TestAccRedshiftUser_Login(t *testing.T) { Config: testAccRedshiftUserLoginUpdateConfig, Check: resource.ComposeTestCheckFunc( testAccCheckRedshiftUserExists("hashed_password"), + resource.TestCheckResourceAttr("redshift_user.with_hashed_password", "password", "md5ad3b897bab2474bc7e408326cb18c42f"), testAccCheckRedshiftUserCanLogin("hashed_password", "Foobarbaz6"), ), }, @@ -182,7 +185,6 @@ resource "redshift_user" "update_user" { testAccCheckRedshiftUserExists("update_user"), resource.TestCheckResourceAttr("redshift_user.update_user", "name", "update_user"), resource.TestCheckResourceAttr("redshift_user.update_user", "connection_limit", "-1"), - resource.TestCheckResourceAttr("redshift_user.update_user", "password", "Foobarbaz1"), resource.TestCheckResourceAttr("redshift_user.update_user", "valid_until", "2038-01-04 12:00:00+00"), resource.TestCheckResourceAttr("redshift_user.update_user", "syslog_access", "RESTRICTED"), resource.TestCheckResourceAttr("redshift_user.update_user", "create_database", "false"), @@ -196,7 +198,6 @@ resource "redshift_user" "update_user" { "redshift_user.update_user", "name", "update_user2", ), resource.TestCheckResourceAttr("redshift_user.update_user", "connection_limit", "5"), - resource.TestCheckResourceAttr("redshift_user.update_user", "password", "Foobarbaz5"), resource.TestCheckResourceAttr("redshift_user.update_user", "valid_until", "infinity"), resource.TestCheckResourceAttr("redshift_user.update_user", "syslog_access", "UNRESTRICTED"), resource.TestCheckResourceAttr("redshift_user.update_user", "create_database", "true"), @@ -209,7 +210,6 @@ resource "redshift_user" "update_user" { testAccCheckRedshiftUserExists("update_user"), resource.TestCheckResourceAttr("redshift_user.update_user", "name", "update_user"), resource.TestCheckResourceAttr("redshift_user.update_user", "connection_limit", "-1"), - resource.TestCheckResourceAttr("redshift_user.update_user", "password", "Foobarbaz1"), resource.TestCheckResourceAttr("redshift_user.update_user", "valid_until", "2038-01-04 12:00:00+00"), resource.TestCheckResourceAttr("redshift_user.update_user", "syslog_access", "RESTRICTED"), resource.TestCheckResourceAttr("redshift_user.update_user", "create_database", "false"), From 10258fe8d6580311f62d0b36f3f12d012df87d17 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 25 Aug 2025 13:17:05 +0200 Subject: [PATCH 31/34] add acc test helper function --- redshift/acc_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/redshift/acc_test.go b/redshift/acc_test.go index 99cd7091..86ee8c8a 100644 --- a/redshift/acc_test.go +++ b/redshift/acc_test.go @@ -7,6 +7,8 @@ import ( "os" "strings" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" ) // Get the value of an environment variable, or skip the @@ -25,3 +27,7 @@ func tfArray(s []string) string { tokens := strings.Split(semiformat, " ") return strings.Join(tokens, ",") } + +func generateRandomObjectName(prefix string) string { + return strings.ReplaceAll(acctest.RandomWithPrefix(prefix), "-", "_") +} From 2ec3c143fa3a857d49418942812bb73407a7279c Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 25 Aug 2025 13:17:18 +0200 Subject: [PATCH 32/34] add redshift group membership resource --- redshift/provider.go | 1 + .../resource_redshift_group_membership.go | 204 ++++++++ ...resource_redshift_group_membership_test.go | 492 ++++++++++++++++++ 3 files changed, 697 insertions(+) create mode 100644 redshift/resource_redshift_group_membership.go create mode 100644 redshift/resource_redshift_group_membership_test.go diff --git a/redshift/provider.go b/redshift/provider.go index 66a1e1c1..73b7296a 100644 --- a/redshift/provider.go +++ b/redshift/provider.go @@ -167,6 +167,7 @@ func Provider() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "redshift_user": redshiftUser(), "redshift_group": redshiftGroup(), + "redshift_group_membership": redshiftGroupMembership(), "redshift_schema": redshiftSchema(), "redshift_default_privileges": redshiftDefaultPrivileges(), "redshift_grant": redshiftGrant(), diff --git a/redshift/resource_redshift_group_membership.go b/redshift/resource_redshift_group_membership.go new file mode 100644 index 00000000..31a343c1 --- /dev/null +++ b/redshift/resource_redshift_group_membership.go @@ -0,0 +1,204 @@ +package redshift + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/lib/pq" +) + +func redshiftGroupMembership() *schema.Resource { + return &schema.Resource{ + Description: fmt.Sprintf(` +Manages Redshift group memberships. Allows either to exclusively manage group memberships or to add members to an existing group. Note: this resource conflicts with the %s attribute of the %s resource +`, "`users`", "`redshift_group`"), + CreateContext: ResourceFunc(resourceRedshiftGroupMembershipCreate), + ReadContext: ResourceFunc(resourceRedshiftGroupMembershipRead), + UpdateContext: ResourceFunc(resourceRedshiftGroupMembershipUpdate), + DeleteContext: ResourceFunc(resourceRedshiftGroupMembershipDelete), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + groupNameAttr: { + Type: schema.TypeString, + Required: true, + Description: "Name of the user group.", + ValidateFunc: validation.StringLenBetween(1, 127), + }, + groupUsersAttr: { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of the user names to add to the group. Note: this resource does not check whether the specified users exist.", + }, + }, + } +} + +func resourceRedshiftGroupMembershipCreate(db *DBConnection, d *schema.ResourceData) error { + groupName := d.Get(groupNameAttr).(string) + userNames := parseUserNames(d.Get(groupUsersAttr)) + + if len(userNames) == 0 { + return fmt.Errorf("at least one user must be specified in %q", groupUsersAttr) + } + + if err := addUsersToGroup(db, groupName, userNames); err != nil { + return err + } + + return resourceRedshiftGroupMembershipRead(db, d) +} + +func addUsersToGroup(db *DBConnection, group string, userNames []string) error { + if len(userNames) == 0 { + return nil + } + userNamesParam := buildUserStringArray(userNames, false) + query := fmt.Sprintf("ALTER GROUP %s ADD USER %s;", pq.QuoteIdentifier(group), userNamesParam) + + if _, err := db.Exec(query); err != nil { + return fmt.Errorf("could not add users %s to group %q: %w", userNamesParam, group, err) + } + return nil + +} + +func resourceRedshiftGroupMembershipRead(db *DBConnection, d *schema.ResourceData) error { + groupName := d.Get(groupNameAttr).(string) + userNames := parseUserNames(d.Get(groupUsersAttr)) + + userNamesParam := buildUserStringArray(userNames, true) + + query := fmt.Sprintf( + `SELECT 1 FROM pg_group pgg JOIN pg_user pgu ON pgu.usesysid = ANY(pgg.grolist) WHERE pgg.groname = %s AND pgu.usename IN (%s);`, + pq.QuoteLiteral(groupName), userNamesParam, + ) + + rows, err := db.Query(query) + if err != nil { + return err + } + defer rows.Close() + if rows.Next() { + if err = rows.Err(); err != nil { + return fmt.Errorf("could not read group membership for group %q: %w", groupName, err) + } + d.SetId(generateGroupMembershipId(groupName, userNames)) + } else { + d.SetId("") + } + return nil +} + +func resourceRedshiftGroupMembershipUpdate(db *DBConnection, d *schema.ResourceData) error { + rawUserNamesOld, rawUserNamesNew := d.GetChange(groupUsersAttr) + oldUserNames := parseUserNames(rawUserNamesOld) + newUserNames := parseUserNames(rawUserNamesNew) + if len(newUserNames) == 0 { + return fmt.Errorf("at least one user must be specified in %q", groupUsersAttr) + } + if d.HasChange(groupNameAttr) { + if err := resourceRedshiftGroupMembershipDelete(db, d); err != nil { + return fmt.Errorf("error deleting group membership while updating the resource: %w", err) + } + if err := resourceRedshiftGroupMembershipCreate(db, d); err != nil { + return fmt.Errorf("error creating group membership while updating the resource: %w", err) + } + return nil + } + deletedUserNames, addedUserNames := calculateUserNamesDiff(oldUserNames, newUserNames) + if err := dropUsersFromGroup(db, d.Get(groupNameAttr).(string), deletedUserNames); err != nil { + return fmt.Errorf("error removing users from group while updating the resource: %w", err) + } + if err := addUsersToGroup(db, d.Get(groupNameAttr).(string), addedUserNames); err != nil { + return fmt.Errorf("error adding users to group while updating the resource: %w", err) + } + return resourceRedshiftGroupMembershipRead(db, d) +} + +func calculateUserNamesDiff(oldUserNames, newUserNames []string) (deletedUserNames, addedUserNames []string) { + deletedUserNames = make([]string, 0) + addedUserNames = make([]string, 0) + for _, oldUserName := range oldUserNames { + found := false + for _, newUserName := range newUserNames { + if oldUserName == newUserName { + found = true + break + } + } + if !found { + deletedUserNames = append(deletedUserNames, oldUserName) + } + } + for _, newUserName := range newUserNames { + found := false + for _, oldUserName := range oldUserNames { + if newUserName == oldUserName { + found = true + break + } + } + if !found { + addedUserNames = append(addedUserNames, newUserName) + } + } + return deletedUserNames, addedUserNames +} + +func resourceRedshiftGroupMembershipDelete(db *DBConnection, d *schema.ResourceData) error { + groupName := d.Get(groupNameAttr).(string) + userNames := parseUserNames(d.Get(groupUsersAttr)) + + return dropUsersFromGroup(db, groupName, userNames) +} + +func dropUsersFromGroup(db *DBConnection, groupName string, userNames []string) error { + if len(userNames) == 0 { + return nil + } + userNamesParam := buildUserStringArray(userNames, false) + query := fmt.Sprintf("ALTER GROUP %s DROP USER %s;", pq.QuoteIdentifier(groupName), userNamesParam) + + if _, err := db.Exec(query); err != nil { + return fmt.Errorf("could not remove users %s from group %q: %w", userNamesParam, groupName, err) + } + return nil +} + +func parseUserNames(rawUserNames interface{}) []string { + rawUserNamesTyped := rawUserNames.(*schema.Set).List() + userNames := make([]string, len(rawUserNamesTyped)) + for index, userNameRaw := range rawUserNamesTyped { + userNames[index] = userNameRaw.(string) + } + return userNames +} + +func buildUserStringArray(userNames []string, encodeAsLiteral bool) string { + var userNamesSafe []string + for _, userName := range userNames { + encodedUserName := strings.ToLower(userName) + if encodeAsLiteral { + encodedUserName = pq.QuoteLiteral(encodedUserName) + } else { + encodedUserName = pq.QuoteIdentifier(encodedUserName) + } + userNamesSafe = append(userNamesSafe, encodedUserName) + } + return strings.Join(userNamesSafe, ", ") +} + +func generateGroupMembershipId(groupName string, userNames []string) string { + var idBuilder strings.Builder + idBuilder.WriteString(groupName) + for _, userName := range userNames { + idBuilder.WriteString("_") + idBuilder.WriteString(strings.ToLower(userName)) + } + return idBuilder.String() +} diff --git a/redshift/resource_redshift_group_membership_test.go b/redshift/resource_redshift_group_membership_test.go new file mode 100644 index 00000000..a2b7b277 --- /dev/null +++ b/redshift/resource_redshift_group_membership_test.go @@ -0,0 +1,492 @@ +package redshift + +import ( + "database/sql" + "errors" + "fmt" + "reflect" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccRedshiftGroupMembership_Basic(t *testing.T) { + groupName := generateRandomObjectName("tf_acc_group_membership") + userName := generateRandomObjectName("tf_acc_group_membership_user") + config := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} + +resource "redshift_group_membership" "simple" { + name = redshift_group.simple.name + users = [redshift_user.simple.name] +} +`, groupName, userName) + updateConfig := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} +`, groupName, userName) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckRedshiftGroupMembershipDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("redshift_group_membership.simple", "name", groupName), + resource.TestCheckResourceAttr("redshift_group_membership.simple", "users.0", userName), + testAccCheckRedshiftGroupMembershipPresence(groupName, userName, true), + ), + }, + { + Config: updateConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRedshiftGroupMembershipPresence(groupName, userName, false), + ), + }, + }, + }) +} + +func TestAccRedshiftGroupMembership_UserRemove(t *testing.T) { + groupName := generateRandomObjectName("tf_acc_group_membership") + userName := generateRandomObjectName("tf_acc_group_membership_user") + config1 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} +`, groupName, userName) + config2 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} + +resource "redshift_group_membership" "simple" { + name = redshift_group.simple.name + users = [%[2]q] +} +`, groupName, userName) + config3 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_group_membership" "simple" { + name = redshift_group.simple.name + users = [%[2]q] +} +`, groupName, userName) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckRedshiftGroupMembershipDestroy, + Steps: []resource.TestStep{ + { + Config: config1, + }, + { + Config: config2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("redshift_group_membership.simple", "name", groupName), + resource.TestCheckResourceAttr("redshift_group_membership.simple", "users.0", userName), + testAccCheckRedshiftGroupMembershipPresence(groupName, userName, true), + ), + }, + { + Config: config3, + ExpectError: regexp.MustCompile("(?s)After applying this test step and performing a `terraform refresh`, the plan was not empty.*redshift_group_membership.simple will be created"), + }, + }, + }) +} + +func TestAccRedshiftGroupMembership_Update(t *testing.T) { + groupName := generateRandomObjectName("tf_acc_group_membership") + newGroupName := generateRandomObjectName("tf_acc_group_membership") + userName1 := generateRandomObjectName("tf_acc_group_membership_user") + userName2 := generateRandomObjectName("tf_acc_group_membership_user") + userName3 := generateRandomObjectName("tf_acc_group_membership_user") + config1 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} + +resource "redshift_group_membership" "simple" { + name = redshift_group.simple.name + users = [redshift_user.simple.name] +} +`, groupName, userName1) + config2 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} + +resource "redshift_user" "also_simple" { + name = %[3]q +} + +resource "redshift_group_membership" "simple" { + name = redshift_group.simple.name + users = [redshift_user.simple.name, redshift_user.also_simple.name] +} +`, newGroupName, userName1, userName2) + config3 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} + +resource "redshift_user" "also_simple" { + name = %[3]q +} + +resource "redshift_user" "third_simple" { + name = %[4]q +} + +resource "redshift_group_membership" "simple" { + name = redshift_group.simple.name + users = [redshift_user.simple.name, redshift_user.third_simple.name] +} +`, newGroupName, userName1, userName2, userName3) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckRedshiftGroupMembershipDestroy, + Steps: []resource.TestStep{ + { + Config: config1, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("redshift_group_membership.simple", "name", groupName), + resource.TestCheckResourceAttr("redshift_group_membership.simple", "users.0", userName1), + testAccCheckRedshiftGroupMembershipPresence(groupName, userName1, true), + ), + }, + { + Config: config2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("redshift_group_membership.simple", "name", newGroupName), + resource.TestCheckTypeSetElemAttr("redshift_group_membership.simple", "users.*", userName1), + resource.TestCheckTypeSetElemAttr("redshift_group_membership.simple", "users.*", userName2), + testAccCheckRedshiftGroupMembershipPresence(newGroupName, userName1, true), + testAccCheckRedshiftGroupMembershipPresence(newGroupName, userName2, true), + testAccCheckRedshiftGroupMembershipPresence(newGroupName, userName3, false), + ), + }, + { + Config: config3, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("redshift_group_membership.simple", "name", newGroupName), + resource.TestCheckTypeSetElemAttr("redshift_group_membership.simple", "users.*", userName1), + resource.TestCheckTypeSetElemAttr("redshift_group_membership.simple", "users.*", userName3), + testAccCheckRedshiftGroupMembershipPresence(newGroupName, userName1, true), + testAccCheckRedshiftGroupMembershipPresence(newGroupName, userName2, false), + testAccCheckRedshiftGroupMembershipPresence(newGroupName, userName3, true), + ), + }, + }, + }) +} + +func TestAccRedshiftGroupMembership_Invalid_EmptyGroupName(t *testing.T) { + groupName := generateRandomObjectName("tf_acc_group_membership") + userName := generateRandomObjectName("tf_acc_group_membership_user") + config1 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} + +resource "redshift_group_membership" "simple" { + name = "" + users = [redshift_user.simple.name] +} +`, groupName, userName) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckRedshiftGroupMembershipDestroy, + Steps: []resource.TestStep{ + { + Config: config1, + ExpectError: regexp.MustCompile("expected length of name to be in the range"), + }, + }, + }) +} + +func TestAccRedshiftGroupMembership_Invalid_EmptyUserList(t *testing.T) { + groupName := generateRandomObjectName("tf_acc_group_membership") + userName := generateRandomObjectName("tf_acc_group_membership_user") + config1 := fmt.Sprintf(` +resource "redshift_group" "simple" { + name = %[1]q + + lifecycle { + ignore_changes = [ + users + ] + } +} + +resource "redshift_user" "simple" { + name = %[2]q +} + +resource "redshift_group_membership" "simple" { + name = redshift_group.simple.name + users = [] +} +`, groupName, userName) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckRedshiftGroupMembershipDestroy, + Steps: []resource.TestStep{ + { + Config: config1, + ExpectError: regexp.MustCompile("at least one user must be specified in \"users\""), + }, + }, + }) +} + +func testAccCheckRedshiftGroupMembershipPresence(groupName, userName string, shouldBePresent bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*Client) + + exists, err := checkGroupMembershipExists(client, groupName, userName) + if err != nil { + return fmt.Errorf("error checking user: %w", err) + } + + if shouldBePresent && !exists { + return fmt.Errorf("user %s should be present in group %s, but it is not", userName, groupName) + } else if !shouldBePresent && exists { + return fmt.Errorf("user %s should not be present in group %s, but it is", userName, groupName) + } + + return nil + } +} + +func checkGroupMembershipExists(client *Client, groupName string, userNames ...string) (bool, error) { + db, err := client.Connect() + if err != nil { + return false, err + } + var _rez int + if len(userNames) == 0 { + return false, nil + } + userNamesParam := buildUserStringArray(userNames, true) + query := fmt.Sprintf(`SELECT 1 FROM pg_group pgg JOIN pg_user pgu ON pgu.usesysid = ANY(pgg.grolist) WHERE pgu.usename IN (%s) AND pgg.groname = $1`, userNamesParam) + err = db.QueryRow(query, groupName).Scan(&_rez) + + switch { + case errors.Is(err, sql.ErrNoRows): + return false, nil + case err != nil: + return false, fmt.Errorf("error reading info about group: %w", err) + } + + return true, nil +} + +func testAccCheckRedshiftGroupMembershipDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "redshift_group_membership" { + continue + } + + exists, err := checkGroupMembershipExists(client, rs.Primary.ID) + + if err != nil { + return fmt.Errorf("error checking role: %w", err) + } + + if exists { + return fmt.Errorf("group still exists after destroy") + } + } + + return nil +} + +func Test_calculateUserNamesDiff(t *testing.T) { + type args struct { + oldUserNames []string + newUserNames []string + } + tests := []struct { + name string + args args + wantDeletedUserNames []string + wantAddedUserNames []string + }{ + { + "no changes", + args{ + []string{ + "user1", + "user2", + }, + []string{ + "user1", + "user2", + }, + }, + []string{}, + []string{}, + }, + { + "add user", + args{ + []string{ + "user1", + "user2", + }, + []string{ + "user1", + "user2", + "user3", + }, + }, + []string{}, + []string{"user3"}, + }, + { + "remove user", + args{ + []string{ + "user1", + "user2", + "user3", + }, + []string{ + "user1", + "user2", + }, + }, + []string{"user3"}, + []string{}, + }, + { + "add and remove user", + args{ + []string{ + "user1", + "user2", + }, + []string{ + "user2", + "user3", + }, + }, + []string{"user1"}, + []string{"user3"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotDeletedUserNames, gotAddedUserNames := calculateUserNamesDiff(tt.args.oldUserNames, tt.args.newUserNames) + if !reflect.DeepEqual(gotDeletedUserNames, tt.wantDeletedUserNames) { + t.Errorf("calculateUserNamesDiff() gotDeletedUserNames = %v, want %v", gotDeletedUserNames, tt.wantDeletedUserNames) + } + if !reflect.DeepEqual(gotAddedUserNames, tt.wantAddedUserNames) { + t.Errorf("calculateUserNamesDiff() gotAddedUserNames = %v, want %v", gotAddedUserNames, tt.wantAddedUserNames) + } + }) + } +} From 333893df9a4cc02b05c45fe80d29b467058b1ae2 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 25 Aug 2025 13:17:45 +0200 Subject: [PATCH 33/34] add group membership docs --- docs/resources/group_membership.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/resources/group_membership.md diff --git a/docs/resources/group_membership.md b/docs/resources/group_membership.md new file mode 100644 index 00000000..b33ef59e --- /dev/null +++ b/docs/resources/group_membership.md @@ -0,0 +1,25 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "redshift_group_membership Resource - terraform-provider-redshift" +subcategory: "" +description: |- + Manages Redshift group memberships. Allows either to exclusively manage group memberships or to add members to an existing group. Note: this resource conflicts with the users attribute of the redshift_group resource +--- + +# redshift_group_membership (Resource) + +Manages Redshift group memberships. Allows either to exclusively manage group memberships or to add members to an existing group. Note: this resource conflicts with the `users` attribute of the `redshift_group` resource + + + + +## Schema + +### Required + +- `name` (String) Name of the user group. +- `users` (Set of String) List of the user names to add to the group. Note: this resource does not check whether the specified users exist. + +### Read-Only + +- `id` (String) The ID of this resource. From 747b3792c749906aa576bda81383123e9ccd96c9 Mon Sep 17 00:00:00 2001 From: Michael Bruns Date: Mon, 25 Aug 2025 13:20:35 +0200 Subject: [PATCH 34/34] add example for group membership resource --- examples/resources/redshift_group_membership/resource.tf | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 examples/resources/redshift_group_membership/resource.tf diff --git a/examples/resources/redshift_group_membership/resource.tf b/examples/resources/redshift_group_membership/resource.tf new file mode 100644 index 00000000..0b4e7b1f --- /dev/null +++ b/examples/resources/redshift_group_membership/resource.tf @@ -0,0 +1,4 @@ +resource "redshift_group_membership" "simple" { + name = "some_group_name" + users = ["user1", "user2"] +}