mirror of
https://github.com/minio/minio.git
synced 2024-12-25 22:55:54 -05:00
ae4361cc45
Validate Minio config by checking if there is double json key in any scope level. The returned error contains the json path to the duplicated key.
264 lines
6.8 KiB
Go
264 lines
6.8 KiB
Go
/*
|
|
* Minio Cloud Storage, (C) 2014-2016 Minio, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// PostgreSQL Notifier implementation. A table with a specific
|
|
// structure (column names, column types, and primary key/uniqueness
|
|
// constraint) is used. The user may set the table name in the
|
|
// configuration. A sample SQL command that creates a command with the
|
|
// required structure is:
|
|
//
|
|
// CREATE TABLE myminio (
|
|
// key VARCHAR PRIMARY KEY,
|
|
// value JSONB
|
|
// );
|
|
//
|
|
// PostgreSQL's "INSERT ... ON CONFLICT ... DO UPDATE ..." feature
|
|
// (UPSERT) is used here, so the minimum version of PostgreSQL
|
|
// required is 9.5.
|
|
//
|
|
// On each create or update object event in Minio Object storage
|
|
// server, a row is created or updated in the table in Postgres. On
|
|
// each object removal, the corresponding row is deleted from the
|
|
// table.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
// libpq db driver is usually imported blank - see examples in
|
|
// https://godoc.org/github.com/lib/pq
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
const (
|
|
upsertRow = `INSERT INTO %s (key, value)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (key)
|
|
DO UPDATE SET value = EXCLUDED.value;`
|
|
deleteRow = ` DELETE FROM %s
|
|
WHERE key = $1;`
|
|
createTable = `CREATE TABLE %s (
|
|
key VARCHAR PRIMARY KEY,
|
|
value JSONB
|
|
);`
|
|
tableExists = `SELECT 1 FROM %s;`
|
|
)
|
|
|
|
type postgreSQLNotify struct {
|
|
Enable bool `json:"enable"`
|
|
|
|
// pass connection string in config directly. This string is
|
|
// formatted according to
|
|
// https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters
|
|
ConnectionString string `json:"connectionString"`
|
|
// specifying a table name is required.
|
|
Table string `json:"table"`
|
|
|
|
// uses the values below if no connection string is specified
|
|
// - however the connection string method offers more
|
|
// flexibility.
|
|
Host string `json:"host"`
|
|
Port string `json:"port"`
|
|
User string `json:"user"`
|
|
Password string `json:"password"`
|
|
Database string `json:"database"`
|
|
}
|
|
|
|
func (p *postgreSQLNotify) Validate() error {
|
|
if !p.Enable {
|
|
return nil
|
|
}
|
|
if _, err := checkNetURL(p.Host); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type pgConn struct {
|
|
connStr string
|
|
table string
|
|
preparedStmts map[string]*sql.Stmt
|
|
*sql.DB
|
|
}
|
|
|
|
func dialPostgreSQL(pgN postgreSQLNotify) (pgConn, error) {
|
|
if !pgN.Enable {
|
|
return pgConn{}, errNotifyNotEnabled
|
|
}
|
|
|
|
// check that table is specified
|
|
if pgN.Table == "" {
|
|
return pgConn{}, fmt.Errorf(
|
|
"PostgreSQL Notifier Error: Table was not specified in configuration")
|
|
}
|
|
|
|
connStr := pgN.ConnectionString
|
|
// check if connection string is specified
|
|
if connStr == "" {
|
|
// build from other parameters
|
|
params := []string{}
|
|
if pgN.Host != "" {
|
|
params = append(params, "host="+pgN.Host)
|
|
}
|
|
if pgN.Port != "" {
|
|
params = append(params, "port="+pgN.Port)
|
|
}
|
|
if pgN.User != "" {
|
|
params = append(params, "user="+pgN.User)
|
|
}
|
|
if pgN.Password != "" {
|
|
params = append(params, "password="+pgN.Password)
|
|
}
|
|
if pgN.Database != "" {
|
|
params = append(params, "dbname="+pgN.Database)
|
|
}
|
|
connStr = strings.Join(params, " ")
|
|
}
|
|
|
|
db, err := sql.Open("postgres", connStr)
|
|
if err != nil {
|
|
return pgConn{}, fmt.Errorf(
|
|
"PostgreSQL Notifier Error: Connection opening failure (connectionString=%s): %v",
|
|
connStr, err,
|
|
)
|
|
}
|
|
|
|
// ping to check that server is actually reachable.
|
|
err = db.Ping()
|
|
if err != nil {
|
|
return pgConn{}, fmt.Errorf(
|
|
"PostgreSQL Notifier Error: Ping to server failed with: %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
// check that table exists - if not, create it.
|
|
_, err = db.Exec(fmt.Sprintf(tableExists, pgN.Table))
|
|
if err != nil {
|
|
// most likely, table does not exist. try to create it:
|
|
_, errCreate := db.Exec(fmt.Sprintf(createTable, pgN.Table))
|
|
if errCreate != nil {
|
|
// failed to create the table. error out.
|
|
return pgConn{}, fmt.Errorf(
|
|
"PostgreSQL Notifier Error: 'Select' failed with %v, then 'Create Table' failed with %v",
|
|
err, errCreate,
|
|
)
|
|
}
|
|
}
|
|
|
|
// create prepared statements
|
|
stmts := make(map[string]*sql.Stmt)
|
|
// insert or update statement
|
|
stmts["upsertRow"], err = db.Prepare(fmt.Sprintf(upsertRow, pgN.Table))
|
|
if err != nil {
|
|
return pgConn{},
|
|
fmt.Errorf("PostgreSQL Notifier Error: create UPSERT prepared statement failed with: %v", err)
|
|
}
|
|
stmts["deleteRow"], err = db.Prepare(fmt.Sprintf(deleteRow, pgN.Table))
|
|
if err != nil {
|
|
return pgConn{},
|
|
fmt.Errorf("PostgreSQL Notifier Error: create DELETE prepared statement failed with: %v", err)
|
|
}
|
|
|
|
return pgConn{connStr, pgN.Table, stmts, db}, nil
|
|
}
|
|
|
|
func newPostgreSQLNotify(accountID string) (*logrus.Logger, error) {
|
|
pgNotify := serverConfig.Notify.GetPostgreSQLByID(accountID)
|
|
|
|
// Dial postgres
|
|
pgC, err := dialPostgreSQL(pgNotify)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pgLog := logrus.New()
|
|
|
|
pgLog.Out = ioutil.Discard
|
|
|
|
pgLog.Formatter = new(logrus.JSONFormatter)
|
|
|
|
pgLog.Hooks.Add(pgC)
|
|
|
|
return pgLog, nil
|
|
}
|
|
|
|
func (pgC pgConn) Close() {
|
|
// first close all prepared statements
|
|
for _, v := range pgC.preparedStmts {
|
|
_ = v.Close()
|
|
}
|
|
// close db connection
|
|
_ = pgC.DB.Close()
|
|
}
|
|
|
|
func (pgC pgConn) Fire(entry *logrus.Entry) error {
|
|
// get event type by trying to convert to string
|
|
entryEventType, ok := entry.Data["EventType"].(string)
|
|
if !ok {
|
|
// ignore event if converting EventType to string
|
|
// fails.
|
|
return nil
|
|
}
|
|
|
|
// Check for event delete
|
|
if eventMatch(entryEventType, []string{"s3:ObjectRemoved:*"}) {
|
|
// delete row from the table
|
|
_, err := pgC.preparedStmts["deleteRow"].Exec(entry.Data["Key"])
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Error deleting event with key = %v - got postgres error - %v",
|
|
entry.Data["Key"], err,
|
|
)
|
|
}
|
|
} else {
|
|
// json encode the value for the row
|
|
value, err := json.Marshal(map[string]interface{}{
|
|
"Records": entry.Data["Records"],
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Unable to encode event %v to JSON - got error - %v",
|
|
entry.Data["Records"], err,
|
|
)
|
|
}
|
|
|
|
// upsert row into the table
|
|
_, err = pgC.preparedStmts["upsertRow"].Exec(entry.Data["Key"], value)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Unable to upsert event with Key=%v and Value=%v - got postgres error - %v",
|
|
entry.Data["Key"], entry.Data["Records"], err,
|
|
)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (pgC pgConn) Levels() []logrus.Level {
|
|
return []logrus.Level{
|
|
logrus.InfoLevel,
|
|
}
|
|
}
|