Skip to content
This repository has been archived by the owner on Jan 12, 2021. It is now read-only.
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
package mysql
import (
"database/sql"
"fmt"
"net"
"net/url"
"regexp"
"strings"
"time"
"github.com/go-sql-driver/mysql"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"golang.org/x/net/proxy"
)
const (
cleartextPasswords = "cleartext"
nativePasswords = "native"
)
type MySQLConfiguration struct {
Config *mysql.Config
MaxConnLifetime time.Duration
MaxOpenConns int
}
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"endpoint": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("MYSQL_ENDPOINT", nil),
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value == "" {
errors = append(errors, fmt.Errorf("Endpoint must not be an empty string"))
}
return
},
},
"username": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("MYSQL_USERNAME", nil),
},
"password": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("MYSQL_PASSWORD", nil),
},
"proxy": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"ALL_PROXY",
"all_proxy",
}, nil),
ValidateFunc: validation.StringMatch(regexp.MustCompile("^socks5h?://.*:\\d+$"), "The proxy URL is not a valid socks url."),
},
"tls": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("MYSQL_TLS_CONFIG", "false"),
ValidateFunc: validation.StringInSlice([]string{
"true",
"false",
"skip-verify",
}, false),
},
"max_conn_lifetime_sec": {
Type: schema.TypeInt,
Optional: true,
},
"max_open_conns": {
Type: schema.TypeInt,
Optional: true,
},
"authentication_plugin": {
Type: schema.TypeString,
Optional: true,
Default: nativePasswords,
ValidateFunc: validation.StringInSlice([]string{cleartextPasswords, nativePasswords}, true),
},
},
ResourcesMap: map[string]*schema.Resource{
"mysql_database": resourceDatabase(),
"mysql_grant": resourceGrant(),
"mysql_role": resourceRole(),
"mysql_user": resourceUser(),
"mysql_user_password": resourceUserPassword(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
var endpoint = d.Get("endpoint").(string)
proto := "tcp"
if len(endpoint) > 0 && endpoint[0] == '/' {
proto = "unix"
}
conf := mysql.Config{
User: d.Get("username").(string),
Passwd: d.Get("password").(string),
Net: proto,
Addr: endpoint,
TLSConfig: d.Get("tls").(string),
AllowNativePasswords: d.Get("authentication_plugin").(string) == nativePasswords,
AllowCleartextPasswords: d.Get("authentication_plugin").(string) == cleartextPasswords,
}
dialer, err := makeDialer(d)
if err != nil {
return nil, err
}
mysql.RegisterDial("tcp", func(network string) (net.Conn, error) {
return dialer.Dial("tcp", network)
})
return &MySQLConfiguration{
Config: &conf,
MaxConnLifetime: time.Duration(d.Get("max_conn_lifetime_sec").(int)) * time.Second,
MaxOpenConns: d.Get("max_open_conns").(int),
}, nil
}
var identQuoteReplacer = strings.NewReplacer("`", "``")
func makeDialer(d *schema.ResourceData) (proxy.Dialer, error) {
proxyFromEnv := proxy.FromEnvironment()
proxyArg := d.Get("proxy").(string)
if len(proxyArg) > 0 {
proxyURL, err := url.Parse(proxyArg)
if err != nil {
return nil, err
}
proxy, err := proxy.FromURL(proxyURL, proxyFromEnv)
if err != nil {
return nil, err
}
return proxy, nil
}
return proxyFromEnv, nil
}
func quoteIdentifier(in string) string {
return fmt.Sprintf("`%s`", identQuoteReplacer.Replace(in))
}
func serverVersion(db *sql.DB) (*version.Version, error) {
var versionString string
err := db.QueryRow("SELECT @@GLOBAL.innodb_version").Scan(&versionString)
if err != nil {
return nil, err
}
return version.NewVersion(versionString)
}
func serverVersionString(db *sql.DB) (string, error) {
var versionString string
err := db.QueryRow("SELECT @@GLOBAL.version").Scan(&versionString)
if err != nil {
return "", err
}
return versionString, nil
}
func connectToMySQL(conf *MySQLConfiguration) (*sql.DB, error) {
dsn := conf.Config.FormatDSN()
var db *sql.DB
var err error
// When provisioning a database server there can often be a lag between
// when Terraform thinks it's available and when it is actually available.
// This is particularly acute when provisioning a server and then immediately
// trying to provision a database on it.
retryError := resource.Retry(5*time.Minute, func() *resource.RetryError {
db, err = sql.Open("mysql", dsn)
if err != nil {
return resource.RetryableError(err)
}
err = db.Ping()
if err != nil {
return resource.RetryableError(err)
}
return nil
})
if retryError != nil {
return nil, fmt.Errorf("Could not connect to server: %s", retryError)
}
db.SetConnMaxLifetime(conf.MaxConnLifetime)
db.SetMaxOpenConns(conf.MaxOpenConns)
return db, nil
}