142 lines
4.8 KiB
Go
142 lines
4.8 KiB
Go
// Copyright 2015 ISRG. All rights reserved
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package sa
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
|
|
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
|
"github.com/letsencrypt/boulder/core"
|
|
blog "github.com/letsencrypt/boulder/log"
|
|
)
|
|
|
|
// NewDbMap creates the root gorp mapping object. Create one of these for each
|
|
// database schema you wish to map. Each DbMap contains a list of mapped tables.
|
|
// It automatically maps the tables for the primary parts of Boulder around the
|
|
// Storage Authority. This may require some further work when we use a disjoint
|
|
// schema, like that for `certificate-authority-data.go`.
|
|
func NewDbMap(dbConnect string) (*gorp.DbMap, error) {
|
|
logger := blog.GetAuditLogger()
|
|
|
|
var err error
|
|
dbConnect, err = recombineURLForDB(dbConnect)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
logger.Debug("Connecting to database")
|
|
|
|
db, err := sql.Open("mysql", dbConnect)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = db.Ping(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dialect := gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}
|
|
dbmap := &gorp.DbMap{Db: db, Dialect: dialect, TypeConverter: BoulderTypeConverter{}}
|
|
|
|
initTables(dbmap)
|
|
|
|
logger.Debug("Connected to database")
|
|
|
|
return dbmap, err
|
|
}
|
|
|
|
// recombineURLForDB transforms a database URL to a URL-like string
|
|
// that the mysql driver can use. The mysql driver needs the Host data
|
|
// to be wrapped in "tcp()" but url.Parse will escape the parentheses
|
|
// and the mysql driver doesn't understand them. So, we can't have
|
|
// "tcp()" in the configs, but can't leave it out before passing it to
|
|
// the mysql driver. Similarly, the driver needs the password and
|
|
// username unescaped. Compromise by doing the leg work if the config
|
|
// says the database URL's scheme is a fake one called
|
|
// "mysqltcp://". See
|
|
// https://github.com/go-sql-driver/mysql/issues/362 for why we have
|
|
// to futz around and avoid URL.String.
|
|
func recombineURLForDB(dbConnect string) (string, error) {
|
|
dbConnect = strings.TrimSpace(dbConnect)
|
|
dbURL, err := url.Parse(dbConnect)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if dbURL.Scheme != "mysql+tcp" {
|
|
format := "given database connection string was not a mysql+tcp:// URL, was %#v"
|
|
return "", fmt.Errorf(format, dbURL.Scheme)
|
|
}
|
|
|
|
dsnVals, err := url.ParseQuery(dbURL.RawQuery)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
dsnVals.Set("parseTime", "true")
|
|
|
|
// Required to make UPDATE return the number of rows matched,
|
|
// instead of the number of rows changed by the UPDATE.
|
|
dsnVals.Set("clientFoundRows", "true")
|
|
|
|
user := dbURL.User.Username()
|
|
passwd, hasPass := dbURL.User.Password()
|
|
dbConn := ""
|
|
if user != "" {
|
|
dbConn = url.QueryEscape(user)
|
|
}
|
|
if hasPass {
|
|
dbConn += ":" + passwd
|
|
}
|
|
dbConn += "@tcp(" + dbURL.Host + ")"
|
|
// TODO(jmhodges): should be dbURL.EscapedPath() but Travis doesn't have 1.5
|
|
return dbConn + dbURL.Path + "?" + dsnVals.Encode(), nil
|
|
}
|
|
|
|
// SetSQLDebug enables/disables GORP SQL-level Debugging
|
|
func SetSQLDebug(dbMap *gorp.DbMap, state bool) {
|
|
dbMap.TraceOff()
|
|
|
|
if state {
|
|
// Enable logging
|
|
dbMap.TraceOn("SQL: ", &SQLLogger{blog.GetAuditLogger()})
|
|
}
|
|
}
|
|
|
|
// SQLLogger adapts the AuditLogger to a format GORP can use.
|
|
type SQLLogger struct {
|
|
log *blog.AuditLogger
|
|
}
|
|
|
|
// Printf adapts the AuditLogger to GORP's interface
|
|
func (log *SQLLogger) Printf(format string, v ...interface{}) {
|
|
log.log.Debug(fmt.Sprintf(format, v))
|
|
}
|
|
|
|
// initTables constructs the table map for the ORM. If you want to also create
|
|
// the tables, call CreateTablesIfNotExists on the DbMap.
|
|
func initTables(dbMap *gorp.DbMap) {
|
|
regTable := dbMap.AddTableWithName(regModel{}, "registrations").SetKeys(true, "ID")
|
|
regTable.SetVersionCol("LockCol")
|
|
regTable.ColMap("Key").SetNotNull(true)
|
|
regTable.ColMap("KeySHA256").SetNotNull(true).SetUnique(true)
|
|
pendingAuthzTable := dbMap.AddTableWithName(pendingauthzModel{}, "pending_authz").SetKeys(false, "ID")
|
|
pendingAuthzTable.SetVersionCol("LockCol")
|
|
pendingAuthzTable.ColMap("Challenges").SetMaxSize(1536)
|
|
|
|
authzTable := dbMap.AddTableWithName(authzModel{}, "authz").SetKeys(false, "ID")
|
|
authzTable.ColMap("Challenges").SetMaxSize(1536)
|
|
|
|
dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(false, "Serial")
|
|
dbMap.AddTableWithName(core.CertificateStatus{}, "certificateStatus").SetKeys(false, "Serial").SetVersionCol("LockCol")
|
|
dbMap.AddTableWithName(core.OCSPResponse{}, "ocspResponses").SetKeys(true, "ID")
|
|
dbMap.AddTableWithName(core.CRL{}, "crls").SetKeys(false, "Serial")
|
|
dbMap.AddTableWithName(core.DeniedCSR{}, "deniedCSRs").SetKeys(true, "ID")
|
|
}
|