Skip to content

Commit 3060c80

Browse files
committed
refactor provider to not configure the db connectivity when initialising the provider
1 parent ded9101 commit 3060c80

9 files changed

Lines changed: 257 additions & 312 deletions

internal/provider/config.go

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,100 @@
11
package provider
22

33
import (
4+
"context"
45
"database/sql"
56
"fmt"
7+
"net"
8+
"net/url"
69
"sync"
10+
11+
"cloud.google.com/go/cloudsqlconn"
12+
"cloud.google.com/go/cloudsqlconn/postgres/pgxv4"
13+
"github.com/hashicorp/terraform-plugin-log/tflog"
14+
"golang.org/x/net/proxy"
715
)
816

917
type Config struct {
10-
dsnTemplate string
1118
dbRegistry map[string]*sql.DB
1219
dbRegistryMutex sync.Mutex
1320
}
1421

15-
func NewConfig(dsnTemplate string) *Config {
22+
func NewConfig() *Config {
1623
return &Config{
17-
dsnTemplate: dsnTemplate,
18-
dbRegistry: make(map[string]*sql.DB),
24+
dbRegistry: make(map[string]*sql.DB),
1925
}
2026
}
2127

22-
func (c *Config) connectToPostgresqlDb(dbName string) (*sql.DB, error) {
23-
dsn := fmt.Sprintf(c.dsnTemplate, "dbname="+dbName)
24-
return c.connectToPostgresql(dsn)
25-
}
26-
27-
func (c *Config) connectToPostgresqlNoDb() (*sql.DB, error) {
28-
dsn := fmt.Sprintf(c.dsnTemplate, "dbname=postgres")
29-
return c.connectToPostgresql(dsn)
30-
}
31-
32-
func (c *Config) connectToPostgresql(dsn string) (*sql.DB, error) {
28+
func (c *Config) connectToPostgresql(ctx context.Context, cc *ConnectionConfig) (*sql.DB, error) {
3329
c.dbRegistryMutex.Lock()
3430
defer c.dbRegistryMutex.Unlock()
3531

36-
if c.dbRegistry[dsn] != nil {
37-
return c.dbRegistry[dsn], nil
32+
id := cc.Id()
33+
34+
if c.dbRegistry[id] != nil {
35+
return c.dbRegistry[id], nil
36+
}
37+
38+
err := createSqlDriver(ctx, cc)
39+
if err != nil {
40+
return nil, err
3841
}
3942

40-
db, err := sql.Open("cloudsql-postgres", dsn)
43+
db, err := sql.Open(id, cc.Dsn())
4144
if err != nil {
4245
return nil, err
4346
}
44-
c.dbRegistry[dsn] = db
45-
return c.dbRegistry[dsn], nil
47+
48+
c.dbRegistry[id] = db
49+
return c.dbRegistry[id], nil
50+
}
51+
52+
func createSqlDriver(ctx context.Context, cc *ConnectionConfig) error {
53+
var (
54+
dialOptions []cloudsqlconn.DialOption
55+
options []cloudsqlconn.Option
56+
)
57+
58+
if cc.PrivateIP.ValueBool() {
59+
dialOptions = append(dialOptions, cloudsqlconn.WithPrivateIP())
60+
}
61+
62+
if cc.PSC.ValueBool() {
63+
dialOptions = append(dialOptions, cloudsqlconn.WithPSC())
64+
}
65+
66+
options = append(options, cloudsqlconn.WithDefaultDialOptions(dialOptions...))
67+
68+
if !cc.Proxy.IsNull() {
69+
options = append(options, cloudsqlconn.WithDialFunc(createDialer(cc.Proxy.ValueString(), ctx)))
70+
}
71+
72+
_, err := pgxv4.RegisterDriver(cc.Id(), options...)
73+
74+
return err
75+
}
76+
77+
func createDialer(proxyInput string, ctxProvider context.Context) func(ctx context.Context, network, addr string) (net.Conn, error) {
78+
return func(ctx context.Context, network, address string) (net.Conn, error) {
79+
tflog.Info(ctxProvider, "Creating Dialer with proxy: "+proxyInput)
80+
if len(proxyInput) == 0 {
81+
return nil, fmt.Errorf("proxy is empty")
82+
}
83+
84+
proxyURL, err := url.Parse(proxyInput)
85+
if err != nil {
86+
return nil, err
87+
}
88+
d, err := proxy.FromURL(proxyURL, proxy.Direct)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
if xd, ok := d.(proxy.ContextDialer); ok {
94+
return xd.DialContext(ctx, network, address)
95+
}
96+
97+
tflog.Warn(ctxProvider, "net.Conn created without context.Context")
98+
return d.Dial(network, address) // TODO: force use of context?
99+
}
46100
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package provider
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"encoding/json"
7+
"fmt"
8+
"regexp"
9+
10+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
15+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
16+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
17+
"github.com/hashicorp/terraform-plugin-framework/types"
18+
)
19+
20+
type ConnectionConfig struct {
21+
ConnectionName types.String `tfsdk:"connection_name"`
22+
Database types.String `tfsdk:"database"`
23+
Username types.String `tfsdk:"username"`
24+
Password types.String `tfsdk:"password"`
25+
Proxy types.String `tfsdk:"proxy"`
26+
PrivateIP types.Bool `tfsdk:"private_ip"`
27+
PSC types.Bool `tfsdk:"psc"`
28+
SslMode types.String `tfsdk:"ssl_mode"`
29+
// IAMAuthentication types.Bool `tfsdk:"iam_authentication"` # Not supporting IAM authentication for now.
30+
}
31+
32+
func (c *ConnectionConfig) Dsn() string {
33+
sslMode := "disable"
34+
if !c.SslMode.IsNull() {
35+
sslMode = c.SslMode.ValueString()
36+
}
37+
38+
return fmt.Sprintf("host=%s dbname=%s user=%s password=%s sslmode=%s",
39+
c.ConnectionName.ValueString(),
40+
c.Database.ValueString(),
41+
c.Username.ValueString(),
42+
c.Password.ValueString(),
43+
sslMode)
44+
}
45+
46+
func (c *ConnectionConfig) Id() string {
47+
var buf bytes.Buffer
48+
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
49+
json.NewEncoder(encoder).Encode(c)
50+
encoder.Close()
51+
return buf.String()
52+
}
53+
54+
func connectionConfigSchemaAttribute() schema.Attribute {
55+
return schema.SingleNestedAttribute{
56+
Description: "The connection properties for the Cloud SQL instance.",
57+
MarkdownDescription: "The connection properties for the Cloud SQL instance.",
58+
Required: true,
59+
Attributes: map[string]schema.Attribute{
60+
"connection_name": schema.StringAttribute{
61+
MarkdownDescription: "The connection name of the Google Cloud SQL Postgresql instance. The `connection_name` format should be `<project>:<region>:<instance>`",
62+
Description: "The connection name of the Google Cloud SQL Postgresql instance. The connection_name format should be <project>:<region>:<instance>",
63+
Optional: true,
64+
Validators: []validator.String{
65+
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-z0-9\-]+\:[a-z0-9\-]+\:[a-z0-9\-]+$`),
66+
"`connection_name` must have the format of `<project>:<region>:<instance>`"),
67+
},
68+
},
69+
"database": schema.StringAttribute{
70+
Description: "The database to connect to. Defaults to `postgres`.",
71+
MarkdownDescription: "The database to connect to. Defaults to `postgres`.",
72+
Optional: true,
73+
Computed: true,
74+
Default: stringdefault.StaticString("postgres"),
75+
PlanModifiers: []planmodifier.String{
76+
stringplanmodifier.RequiresReplace(),
77+
},
78+
},
79+
"username": schema.StringAttribute{
80+
MarkdownDescription: "The username to use to authenticate with the Cloud SQL Postgresql instance",
81+
Description: "The username to use to authenticate with the Cloud SQL Postgresql instance",
82+
Required: true,
83+
},
84+
"password": schema.StringAttribute{
85+
MarkdownDescription: "The password to use to authenticate using the built-in database authentication",
86+
Description: "The password to use to authenticate using the built-in database authentication",
87+
Required: true,
88+
Sensitive: true,
89+
},
90+
"proxy": schema.StringAttribute{
91+
MarkdownDescription: "Proxy socks url if used. Format needs to be `socks5://<ip>:<port>`",
92+
Description: "Proxy socks url if used. Format needs to be socks5://<ip>:<port>",
93+
Optional: true,
94+
Validators: []validator.String{
95+
stringvalidator.RegexMatches(regexp.MustCompile(`^socks5:\/\/.*:\d+$`),
96+
"`proxy` must have the format of `socks5://<ip>:<port>`"),
97+
},
98+
},
99+
"private_ip": schema.BoolAttribute{
100+
MarkdownDescription: "Use the private IP address of the Cloud SQL Postgresql instance to connect to",
101+
Description: "Use the private IP address of the Cloud SQL Postgresql instance to connect to",
102+
Optional: true,
103+
},
104+
"psc": schema.BoolAttribute{
105+
MarkdownDescription: "Use the Private Service Connect endpoint of the Cloud SQL Postgresql instance to connect to",
106+
Description: "Use the Private Service Connect endpoint of the Cloud SQL Postgresql instance to connect to",
107+
Optional: true,
108+
},
109+
"ssl_mode": schema.StringAttribute{
110+
MarkdownDescription: "Determine the security of the connection to the Cloud SQL Postgresql instance",
111+
Description: "Determine the security of the connection to the Cloud SQL Postgresql instance",
112+
Optional: true,
113+
Validators: []validator.String{
114+
stringvalidator.RegexMatches(regexp.MustCompile(`^(disable|allow|prefer|require)$`),
115+
"`ssl_mode` must be a supported ssl mode. One of 'disable', 'allow', 'prefer' or 'require'"), // TODO: add support for verify-ca and verify-full
116+
},
117+
},
118+
},
119+
PlanModifiers: []planmodifier.Object{
120+
objectplanmodifier.RequiresReplace(),
121+
},
122+
}
123+
}

0 commit comments

Comments
 (0)