flamenco/internal/manager/persistence/initialisation.go
Sybren A. Stüvel 80df8fa6e4 DB Initialisation: try named parameters
Should be tested on Windows, as that's where this code will be used most
often. As of now, untested.
2022-02-22 12:06:54 +01:00

136 lines
4.2 KiB
Go

package persistence
/* ***** BEGIN GPL LICENSE BLOCK *****
*
* Original Code Copyright (C) 2022 Blender Foundation.
*
* This file is part of Flamenco.
*
* Flamenco is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
*
* ***** END GPL LICENSE BLOCK ***** */
import (
"bufio"
"database/sql"
"errors"
"fmt"
"os"
"runtime"
"github.com/rs/zerolog/log"
"golang.org/x/term"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var errInputTooLong = errors.New("input is too long")
const adminDSN = "host=localhost user=postgres password=%s dbname=%s TimeZone=Europe/Amsterdam"
// InitialSetup uses the `postgres` admin user to set up the database.
// TODO: distinguish between production and development setups.
func InitialSetup() error {
// Get the password of the 'postgres' user.
adminPass, err := readPassword()
if err != nil {
return fmt.Errorf("unable to read password: %w", err)
}
// Connect to the 'postgres' database so we can create other databases.
db, err := connectDBAsAdmin(adminPass, "postgres")
if err != nil {
return fmt.Errorf("unable to connect to the database: %w", err)
}
// TODO: get username / password / database name from some config file, user input, CLI args, whatevah.
// Has to be used by the regular Flamenco Manager runs as well, though.
username := "flamenco"
userPass := "flamenco"
tx := db.Exec("CREATE USER @user PASSWORD @pass NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN",
sql.Named("user", username),
sql.Named("pass", userPass))
if tx.Error != nil {
return fmt.Errorf("unable to create database user '%s': %w", username, tx.Error)
}
// Create the databases.
tx = db.Debug().Exec("CREATE DATABASE flamenco OWNER ? ENCODING 'utf8'", username)
if tx.Error != nil {
return fmt.Errorf("unable to create database 'flamenco': %w", tx.Error)
}
tx = db.Exec("CREATE DATABASE flamenco-test OWNER ? ENCODING 'utf8'", username)
if tx.Error != nil {
return fmt.Errorf("unable to create database 'flamenco': %w", tx.Error)
}
// Close the connection so we can reconnect.
sqlDB, err := db.DB()
if err != nil {
fmt.Printf("error closing the database connection, please report this issue: %v", err)
} else {
sqlDB.Close()
}
// Allow 'flamenco' user to completely nuke and recreate the flamenco-test database, without needing 'CREATEDB' permission.
db, err = connectDBAsAdmin(adminPass, "flamenco-test")
if err != nil {
return fmt.Errorf("unable to reconnect to the database: %w", err)
}
tx = db.Exec("ALTER SCHEMA public OWNER TO ?", username)
if tx.Error != nil {
fmt.Printf("Unable to allow database user '%s' to reset the test database: %v\n", username, tx.Error)
fmt.Println("This is not an issue, unless you want to develop Flamenco yourself.")
}
return nil
}
func readPassword() (string, error) {
if pwFromEnv := os.Getenv("PSQL_ADMIN"); pwFromEnv != "" {
log.Info().Msg("getting password from PSQL_ADMIN environment variable")
return pwFromEnv, nil
}
fmt.Print("PostgreSQL admin password: ")
var (
line []byte
err error
)
if runtime.GOOS == "windows" {
// term.ReadPassword() doesn't work reliably on Windows, especially when you
// use a MingW terminal (like Git Bash). See
// https://github.com/golang/go/issues/11914#issuecomment-613715787 for more
// info.
//
// The downside is that this echoes the password to the terminal.
buf := bufio.NewReader(os.Stdin)
line, _, err = buf.ReadLine()
} else {
fd := int(os.Stdin.Fd())
line, err = term.ReadPassword(fd)
}
if err != nil {
return "", err
}
return string(line), nil
}
func connectDBAsAdmin(password, database string) (*gorm.DB, error) {
dsn := fmt.Sprintf(adminDSN, password, database)
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}