diff --git a/cmd/make_mysql_account.go b/cmd/make_mysql_account.go
index 74fee7b..245110a 100644
--- a/cmd/make_mysql_account.go
+++ b/cmd/make_mysql_account.go
@@ -1,11 +1,8 @@
package cmd
import (
- "fmt"
-
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/core"
- "github.com/FAU-CDI/wisski-distillery/pkg/sqle"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/parser"
)
@@ -39,6 +36,8 @@ var errUnableToReadPassword = exit.Error{
}
func (mma makeMysqlAccount) Run(context wisski_distillery.Context) error {
+ dis := context.Environment
+
context.Printf("Username>")
username, err := context.ReadLine()
if err != nil {
@@ -51,20 +50,9 @@ func (mma makeMysqlAccount) Run(context wisski_distillery.Context) error {
return errUnableToReadPassword.WithMessageF(err)
}
- query := sqle.Format("CREATE USER ?@'%' IDENTIFIED BY ?; GRANT ALL PRIVILEGES ON *.* TO ?@`%` WITH GRANT OPTION; FLUSH PRIVILEGES;", username, password, username)
- if err != nil {
- return err
- }
- code, err := context.Environment.SQL().Shell(context.IOStream, "-e", query)
- if err != nil {
+ if err := dis.SQL().CreateSuperuser(username, password, false); err != nil {
return err
}
- if code != 0 {
- return exit.Error{
- ExitCode: exit.ExitCode(uint8(code)),
- Message: fmt.Sprintf("Exit code %d", code),
- }
- }
return nil
}
diff --git a/internal/component/control/html/index.html b/internal/component/control/html/index.html
index a219ee5..1b81a68 100644
--- a/internal/component/control/html/index.html
+++ b/internal/component/control/html/index.html
@@ -25,8 +25,7 @@
GraphDB User Prefix: {{.Config.GraphDBUserPrefix}}
GraphDB Database Prefix: {{.Config.GraphDBRepoPrefix}}
- Bookkeeping Database: {{.Config.DistilleryBookkeepingDatabase}}
- Bookkeeping Table: {{.Config.DistilleryBookkeepingTable}}
+ Bookkeeping Database: {{.Config.DistilleryDatabase}}
Instances
diff --git a/internal/component/control/info.go b/internal/component/control/info.go
index fb9cd7a..b8ca0e0 100644
--- a/internal/component/control/info.go
+++ b/internal/component/control/info.go
@@ -8,9 +8,9 @@ import (
"strings"
"time"
- "github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/config"
+ "github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/tkw1536/goprogram/stream"
"golang.org/x/sync/errgroup"
@@ -98,7 +98,7 @@ func (dis *Control) disIndex(r *http.Request) (idx disIndex, err error) {
type disInstance struct {
Time time.Time
- Instance bookkeeping.Instance
+ Instance models.Instance
Info instances.Info
}
diff --git a/internal/component/instances/instances.go b/internal/component/instances/instances.go
index 2cc4ad4..6948a67 100644
--- a/internal/component/instances/instances.go
+++ b/internal/component/instances/instances.go
@@ -3,10 +3,10 @@ package instances
import (
"errors"
- "github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
"github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/component/triplestore"
+ "github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/tkw1536/goprogram/exit"
"gorm.io/gorm"
"gorm.io/gorm/clause"
@@ -36,17 +36,17 @@ var errSQL = exit.Error{
// It the WissKI does not exist, returns ErrWissKINotFound.
func (instances *Instances) WissKI(slug string) (i WissKI, err error) {
sql := instances.SQL
- if err := sql.Wait(); err != nil {
+ if err := sql.WaitQueryTable(); err != nil {
return i, err
}
- table, err := sql.OpenBookkeeping(false)
+ table, err := sql.QueryTable(false, models.InstanceTable)
if err != nil {
return i, err
}
// find the instance by slug
- query := table.Where(&bookkeeping.Instance{Slug: slug}).Find(&i.Instance)
+ query := table.Where(&models.Instance{Slug: slug}).Find(&i.Instance)
switch {
case query.Error != nil:
return i, errSQL.WithMessageF(query.Error)
@@ -62,11 +62,11 @@ func (instances *Instances) WissKI(slug string) (i WissKI, err error) {
// It does not perform any checks on the WissKI itself.
func (instances *Instances) Has(slug string) (ok bool, err error) {
sql := instances.SQL
- if err := sql.Wait(); err != nil {
+ if err := sql.WaitQueryTable(); err != nil {
return false, err
}
- table, err := sql.OpenBookkeeping(false)
+ table, err := sql.QueryTable(false, models.InstanceTable)
if err != nil {
return false, err
}
@@ -106,12 +106,12 @@ func (instances *Instances) Load(slugs ...string) ([]WissKI, error) {
// find finds instances based on the provided query
func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB) (results []WissKI, err error) {
sql := instances.SQL
- if err := sql.Wait(); err != nil {
+ if err := sql.WaitQueryTable(); err != nil {
return nil, err
}
// open the bookkeeping table
- table, err := sql.OpenBookkeeping(false)
+ table, err := sql.QueryTable(false, models.InstanceTable)
if err != nil {
return nil, err
}
@@ -126,7 +126,7 @@ func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB
}
// fetch bookkeeping instances
- var bks []bookkeeping.Instance
+ var bks []models.Instance
find = find.Find(&bks)
if find.Error != nil {
return nil, errSQL.WithMessageF(find.Error)
diff --git a/internal/component/instances/wisski_db.go b/internal/component/instances/wisski_db.go
index fcaa1b4..533bbf3 100644
--- a/internal/component/instances/wisski_db.go
+++ b/internal/component/instances/wisski_db.go
@@ -1,13 +1,11 @@
package instances
-import (
- "github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
-)
+import "github.com/FAU-CDI/wisski-distillery/internal/models"
// WissKI represents a single WissKI Instance
type WissKI struct {
// Whatever is stored inside the bookkeeping database
- bookkeeping.Instance
+ models.Instance
// Credentials to Drupal
DrupalUsername string
@@ -19,7 +17,7 @@ type WissKI struct {
// Save saves this instance in the bookkeeping table
func (wisski *WissKI) Save() error {
- db, err := wisski.instances.SQL.OpenBookkeeping(false)
+ db, err := wisski.instances.SQL.QueryTable(false, models.InstanceTable)
if err != nil {
return err
}
@@ -35,7 +33,7 @@ func (wisski *WissKI) Save() error {
// Delete deletes this instance from the bookkeeping table
func (wisski *WissKI) Delete() error {
- db, err := wisski.instances.SQL.OpenBookkeeping(false)
+ db, err := wisski.instances.SQL.QueryTable(false, models.InstanceTable)
if err != nil {
return err
}
diff --git a/internal/component/sql/connect.go b/internal/component/sql/connect.go
new file mode 100644
index 0000000..35afe33
--- /dev/null
+++ b/internal/component/sql/connect.go
@@ -0,0 +1,139 @@
+package sql
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "net"
+ "sync/atomic"
+
+ mysqldriver "github.com/go-sql-driver/mysql"
+
+ "gorm.io/driver/mysql"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
+
+ "github.com/FAU-CDI/wisski-distillery/internal/models"
+ "github.com/FAU-CDI/wisski-distillery/pkg/wait"
+)
+
+//
+// ========== low-level connection ==========
+//
+
+// Query performs a database query, outside a database contect
+func (sql *SQL) Query(query string, args ...interface{}) error {
+ // connect to the server
+ conn, err := sql.connect("")
+ if err != nil {
+ return err
+ }
+
+ // do the query!
+ {
+ _, err := conn.Exec(query, args...)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+}
+
+// WaitQuery waits for the query interface to be able to connect to the database
+func (sql *SQL) WaitQuery() error {
+ return wait.Wait(func() bool {
+ err := sql.Query("select 1;")
+ // log.Printf("[WaitQuery] %s\n", err) // debug
+ return err == nil
+ }, sql.PollInterval, sql.PollContext)
+}
+
+//
+// ========== connection via gorm ==========
+//
+
+// QueryTable returns a gorm.DB to connect to the provided gorm database table
+func (sql *SQL) QueryTable(silent bool, name string) (*gorm.DB, error) {
+ conn, err := sql.connect(sql.Config.DistilleryDatabase)
+ if err != nil {
+ return nil, err
+ }
+
+ // gorm configuration
+ config := &gorm.Config{}
+ if silent {
+ config.Logger = logger.Default.LogMode(logger.Silent)
+ }
+
+ // mysql connection
+ cfg := mysql.Config{
+ Conn: conn,
+
+ DefaultStringSize: 256,
+ }
+
+ // open the gorm connection!
+ db, err := gorm.Open(mysql.New(cfg), config)
+ if err != nil {
+ return nil, err
+ }
+
+ // set the table
+ db = db.Table(name)
+
+ // check that nothing went wrong
+ if db.Error != nil {
+ return nil, db.Error
+ }
+ return db, nil
+}
+
+// WaitQueryTable waits for a connection to succeed via QueryTable
+func (sql *SQL) WaitQueryTable() error {
+ return wait.Wait(func() bool {
+ _, err := sql.QueryTable(true, models.InstanceTable)
+ return err == nil
+ }, sql.PollInterval, sql.PollContext)
+}
+
+//
+// ========== low-level database connection ==========
+//
+
+func (ssql *SQL) connect(database string) (*sql.DB, error) {
+ conn, err := sql.Open("mysql", ssql.dsn(database))
+ if err != nil {
+ return nil, err
+ }
+
+ conn.SetMaxIdleConns(0)
+
+ return conn, nil
+}
+
+// dsn returns a dsn fof connecting to the database
+func (sql *SQL) dsn(database string) string {
+ user := sql.Config.MysqlAdminUser
+ pass := sql.Config.MysqlAdminPassword
+ network := sql.network()
+ server := sql.ServerURL
+
+ return fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8&parseTime=True&loc=Local", user, pass, network, server, database)
+}
+
+var proxyNameCounter uint64
+
+// network returns the network to use to connect to the database
+func (sql *SQL) network() string {
+ return sql.lazyNetwork.Get(func() (name string) {
+ network := "tcp"
+
+ // register a new DialContext function to use the environment.
+ // this seems like a bit of a hack, but it works for now.
+ name = fmt.Sprintf("sql-network-%d", atomic.AddUint64(&proxyNameCounter, 1))
+ mysqldriver.RegisterDialContext(name, func(ctx context.Context, addr string) (net.Conn, error) {
+ return sql.Core.Environment.DialContext(ctx, network, addr)
+ })
+ return
+ })
+}
diff --git a/internal/component/sql/connect_shell.go b/internal/component/sql/connect_shell.go
new file mode 100644
index 0000000..e4b317b
--- /dev/null
+++ b/internal/component/sql/connect_shell.go
@@ -0,0 +1 @@
+package sql
diff --git a/internal/component/sql/database.go b/internal/component/sql/database.go
deleted file mode 100644
index fe5075f..0000000
--- a/internal/component/sql/database.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package sql
-
-import (
- "context"
- "errors"
- "fmt"
- "net"
- "sync/atomic"
-
- mysqldriver "github.com/go-sql-driver/mysql"
-
- "github.com/FAU-CDI/wisski-distillery/pkg/sqle"
- "github.com/FAU-CDI/wisski-distillery/pkg/wait"
- "github.com/tkw1536/goprogram/stream"
- "gorm.io/driver/mysql"
- "gorm.io/gorm"
- "gorm.io/gorm/logger"
-)
-
-var proxyNameCounter uint64
-
-// network returns the network to use to connect to the database
-func (sql *SQL) network() string {
- return sql.lazyNetwork.Get(func() (name string) {
- network := "tcp"
-
- // register a new DialContext function to use the environment.
- // this seems like a bit of a hack, but it works for now.
- name = fmt.Sprintf("sql-network-%d", atomic.AddUint64(&proxyNameCounter, 1))
- mysqldriver.RegisterDialContext(name, func(ctx context.Context, addr string) (net.Conn, error) {
- return sql.Core.Environment.DialContext(ctx, network, addr)
- })
- return
- })
-}
-
-// sqlOpen opens a new sql connection to the provided database using the administrative credentials
-func (sql *SQL) openDatabase(database string, config *gorm.Config) (*gorm.DB, error) {
- cfg := mysql.Config{
- DriverName: "mysql",
- DSN: fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8&parseTime=True&loc=Local", sql.Config.MysqlAdminUser, sql.Config.MysqlAdminPassword, sql.network(), sql.ServerURL, database),
- DefaultStringSize: 256,
- }
-
- db, err := gorm.Open(mysql.New(cfg), config)
- if err != nil {
- return db, err
- }
-
- gdb, err := db.DB()
- if err != nil {
- return db, err
- }
- gdb.SetMaxIdleConns(0)
-
- return db, nil
-}
-
-// OpenBookkeeping opens a connection to the bookkeeping database
-func (sql *SQL) OpenBookkeeping(silent bool) (*gorm.DB, error) {
-
- config := &gorm.Config{}
- if silent {
- config.Logger = logger.Default.LogMode(logger.Silent)
- }
-
- // open the database
- db, err := sql.openDatabase(sql.Config.DistilleryBookkeepingDatabase, config)
- if err != nil {
- return nil, err
- }
-
- // load the table
- table := db.Table(sql.Config.DistilleryBookkeepingTable)
- if table.Error != nil {
- return nil, err
- }
-
- return table, nil
-}
-
-// Shell runs a mysql shell command.
-func (sql *SQL) Shell(io stream.IOStream, argv ...string) (int, error) {
- return sql.Stack(sql.Environment).Exec(io, "sql", "mysql", argv...)
-}
-
-// WaitShell waits for the sql database to be reachable via shell
-func (sql *SQL) WaitShell() error {
- n := stream.FromNil()
- return wait.Wait(func() bool {
- code, err := sql.Shell(n, "-e", "show databases;")
- return err == nil && code == 0
- }, sql.PollInterval, sql.PollContext)
-}
-
-// Wait waits for a connection to the bookkeeping table to suceed
-func (sql *SQL) Wait() error {
- return wait.Wait(func() bool {
- _, err := sql.OpenBookkeeping(true)
- return err == nil
- }, sql.PollInterval, sql.PollContext)
-}
-
-var errInvalidDatabaseName = errors.New("SQLProvision: Invalid database name")
-
-// Query performs a raw database query
-func (sql *SQL) Query(query string, args ...interface{}) bool {
- raw := sqle.Format(query, args...)
- code, err := sql.Shell(stream.FromNil(), "-e", raw)
- return err == nil && code == 0
-}
diff --git a/internal/component/sql/provision.go b/internal/component/sql/provision.go
index e2ecf8a..6f147d6 100644
--- a/internal/component/sql/provision.go
+++ b/internal/component/sql/provision.go
@@ -6,47 +6,81 @@ import (
"github.com/FAU-CDI/wisski-distillery/pkg/sqle"
)
-// SQLProvision provisions a new sql database and user
+var errProvisionInvalidDatabaseParams = errors.New("Provision: Invalid parameters")
+var errProvisionInvalidGrant = errors.New("Provision: Grant failed")
+
+// Provision provisions a new sql database and user
func (sql *SQL) Provision(name, user, password string) error {
- // wait for the database
- if err := sql.WaitShell(); err != nil {
+
+ // NOTE(twiesing): We shouldn't use string concat to build sql queries.
+ // But the driver doesn't support using query params for this particular query.
+ // Apparently it's a "feature", see https://github.com/go-sql-driver/mysql/issues/398#issuecomment-169951763.
+
+ // quick and dirty check to make sure that all the names won't sql inject.
+ if !sqle.IsSafeDatabaseLiteral(name) || !sqle.IsSafeDatabaseSingleQuote(user) || !sqle.IsSafeDatabaseSingleQuote(password) {
+ return errProvisionInvalidDatabaseParams
+ }
+
+ // We use the sql shell here, because not only can we not use query params, but the driver outright rejects queries.
+ // Queries of the form "CREATE USER 'test'@'%' IDENTIFIED BY 'test'; FLUSH PRIVILEGES;" return error 1064 when using driver, but are fine with the shell.
+ // This should be fixed eventually, but I have no idea how.
+
+ if err := sql.unsafeWaitShell(); err != nil {
return err
}
- // it's not a safe database name!
- if !sqle.IsSafeDatabaseName(name) {
- return errInvalidDatabaseName
+ query := "CREATE DATABASE `" + name + "`;" +
+ "CREATE USER '" + user + "'@'%' IDENTIFIED BY '" + password + "';" +
+ "GRANT ALL PRIVILEGES ON `" + name + "`.* TO `" + user + "`@`%`; FLUSH PRIVILEGES;"
+ if !sql.unsafeQueryShell(query) {
+ return errProvisionInvalidGrant
}
- // create the database and user!
- if !sql.Query("CREATE DATABASE `"+name+"`; CREATE USER ?@`%` IDENTIFIED BY ?; GRANT ALL PRIVILEGES ON `"+name+"`.* TO ?@`%`; FLUSH PRIVILEGES;", user, password, user) {
- return errors.New("SQLProvision: Failed to create user")
- }
-
- // and done!
return nil
}
-var errSQLPurgeUser = errors.New("unable to delete user")
+var errCreateSuperuserGrant = errors.New("CreateSuperUser: Grant failed")
+
+func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error {
+ // NOTE(twiesing): This function unsafely uses the shell directly to create a superuser.
+ // This is for two reasons:
+ // (1) this is used during bootstraping
+ // (2) The underlying driver doesn't support "GRANT ALL PRIVILEGES"
+ // See also [sql.Provision].
+
+ if !sqle.IsSafeDatabaseSingleQuote(user) || !sqle.IsSafeDatabaseSingleQuote(password) {
+ return errProvisionInvalidDatabaseParams
+ }
+
+ if err := sql.unsafeWaitShell(); err != nil {
+ return err
+ }
+
+ var IfNotExists string
+ if allowExisting {
+ IfNotExists = "IF NOT EXISTS"
+ }
+
+ query := "CREATE USER " + IfNotExists + " '" + user + "'@'%' IDENTIFIED BY '" + password + "';" +
+ "GRANT ALL PRIVILEGES ON *.* TO '" + user + "'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;"
+ if !sql.unsafeQueryShell(query) {
+ return errCreateSuperuserGrant
+ }
+
+ return nil
+}
// SQLPurgeUser deletes the specified user from the database
func (sql *SQL) PurgeUser(user string) error {
- if !sql.Query("DROP USER IF EXISTS ?@`%`; FLUSH PRIVILEGES; ", user) {
- return errSQLPurgeUser
- }
-
- return nil
+ return sql.Query("DROP USER IF EXISTS ?@`%`; FLUSH PRIVILEGES; ", user)
}
-var errSQLPurgeDB = errors.New("unable to drop database")
+var errSQLPurgeDB = errors.New("unable to drop database: unsafe database name")
// SQLPurgeDatabase deletes the specified db from the database
func (sql *SQL) PurgeDatabase(db string) error {
- if !sqle.IsSafeDatabaseName(db) {
+ if !sqle.IsSafeDatabaseLiteral(db) {
return errSQLPurgeDB
}
- if !sql.Query("DROP DATABASE IF EXISTS `" + db + "`") {
- return errSQLPurgeDB
- }
- return nil
+ return sql.Query("DROP DATABASE IF EXISTS `" + db + "`")
}
diff --git a/internal/component/sql/update.go b/internal/component/sql/update.go
index 7e574ff..9372daf 100644
--- a/internal/component/sql/update.go
+++ b/internal/component/sql/update.go
@@ -4,57 +4,82 @@ import (
"errors"
"fmt"
- "github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
+ "github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/FAU-CDI/wisski-distillery/pkg/sqle"
+ "github.com/FAU-CDI/wisski-distillery/pkg/wait"
"github.com/tkw1536/goprogram/stream"
)
+// Shell runs a mysql shell with the provided databases.
+//
+// NOTE(twiesing): This command should not be used to connect to the database or execute queries except in known situations.
+func (sql *SQL) Shell(io stream.IOStream, argv ...string) (int, error) {
+ return sql.Stack(sql.Environment).Exec(io, "sql", "mysql", argv...)
+}
+
+// unsafeWaitShell waits for a connection via the database shell to succeed
+func (sql *SQL) unsafeWaitShell() error {
+ n := stream.FromNil()
+ return wait.Wait(func() bool {
+ code, err := sql.Shell(n, "-e", "select 1;")
+ // log.Printf("[unsafeWaitShell] %d %s\n", code, err) // debug
+ return err == nil && code == 0
+ }, sql.PollInterval, sql.PollContext)
+}
+
+// unsafeQuery shell executes a raw database query.
+func (sql *SQL) unsafeQueryShell(query string) bool {
+ code, err := sql.Shell(stream.FromNil(), "-e", query)
+ return err == nil && code == 0
+}
+
var errSQLUnableToCreateUser = errors.New("unable to create administrative user")
-var errSQLUnsafeDatabaseName = errors.New("bookkeeping database has an unsafe name")
-var errSQLUnableToCreate = errors.New("unable to create bookkeeping database")
+var errSQLUnsafeDatabaseName = errors.New("distillery database has an unsafe name")
// Update initializes or updates the SQL database.
func (sql *SQL) Update(io stream.IOStream) error {
- if err := sql.WaitShell(); err != nil {
- return err
- }
- // create the admin user
- logging.LogMessage(io, "Creating administrative user")
+ // unsafely create the admin user!
{
- username := sql.Config.MysqlAdminUser
- password := sql.Config.MysqlAdminPassword
- if !sql.Query("CREATE USER IF NOT EXISTS ?@'%' IDENTIFIED BY ?; GRANT ALL PRIVILEGES ON *.* TO ?@`%` WITH GRANT OPTION; FLUSH PRIVILEGES;", username, password, username) {
- return errSQLUnableToCreateUser
+ if err := sql.unsafeWaitShell(); err != nil {
+ return err
+ }
+ logging.LogMessage(io, "Creating administrative user")
+ {
+ username := sql.Config.MysqlAdminUser
+ password := sql.Config.MysqlAdminPassword
+ if err := sql.CreateSuperuser(username, password, true); err != nil {
+ return errSQLUnableToCreateUser
+ }
}
}
// create the admin user
logging.LogMessage(io, "Creating sql database")
{
- if !sqle.IsSafeDatabaseName(sql.Config.DistilleryBookkeepingDatabase) {
+ if !sqle.IsSafeDatabaseLiteral(sql.Config.DistilleryDatabase) {
return errSQLUnsafeDatabaseName
}
- createDBSQL := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`;", sql.Config.DistilleryBookkeepingDatabase)
- if !sql.Query(createDBSQL) {
- return errSQLUnableToCreate
+ createDBSQL := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`;", sql.Config.DistilleryDatabase)
+ if err := sql.Query(createDBSQL); err != nil {
+ return err
}
}
// wait for the database to come up
logging.LogMessage(io, "Waiting for database update to be complete")
- sql.Wait()
+ sql.WaitQueryTable()
// open the database
- logging.LogMessage(io, "Migrating bookkeeping table")
+ logging.LogMessage(io, "Migrating instances table")
{
- db, err := sql.OpenBookkeeping(false)
+ db, err := sql.QueryTable(false, models.InstanceTable)
if err != nil {
return fmt.Errorf("unable to access bookkeeping table: %s", err)
}
- if err := db.AutoMigrate(&bookkeeping.Instance{}); err != nil {
+ if err := db.AutoMigrate(&models.Instance{}); err != nil {
return fmt.Errorf("unable to migrate bookkeeping table: %s", err)
}
}
diff --git a/internal/config/config.go b/internal/config/config.go
index bb46338..4f7aa6e 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -57,8 +57,7 @@ type Config struct {
// In addition to the filesystem the WissKI distillery requires a single SQL table.
// It uses this database to store a list of installed things.
- DistilleryBookkeepingDatabase string `env:"DISTILLERY_BOOKKEEPING_DATABASE" default:"distillery" parser:"slug"`
- DistilleryBookkeepingTable string `env:"DISTILLERY_BOOKKEEPING_TABLE" default:"distillery" parser:"slug"`
+ DistilleryDatabase string `env:"DISTILLERY_BOOKKEEPING_DATABASE" default:"distillery" parser:"slug"`
// Various components use password-based-authentication.
// These passwords are generated automatically.
diff --git a/internal/config/config_template b/internal/config/config_template
index bd61ba4..c255d85 100644
--- a/internal/config/config_template
+++ b/internal/config/config_template
@@ -40,11 +40,9 @@ MYSQL_DATABASE_PREFIX=mysql-factory-
GRAPHDB_USER_PREFIX=graphdb-factory-
GRAPHDB_REPO_PREFIX=graphdb-factory-
-# In addition to the filesystem the WissKI distillery requires a single SQL table.
-# It uses this database to store a list of installed things
+# In addition to the filesystem the WissKI distillery requires a 'bookkeeping' database.
+# This is used to store several settings.
DISTILLERY_BOOKKEEPING_DATABASE=distillery
-DISTILLERY_BOOKKEEPING_TABLE=distillery
-
# Various components use password-based-authentication.
# These passwords are generated automatically.
diff --git a/internal/bookkeeping/bookkeeping.go b/internal/models/instances.go
similarity index 87%
rename from internal/bookkeeping/bookkeeping.go
rename to internal/models/instances.go
index cd91628..3b88908 100644
--- a/internal/bookkeeping/bookkeeping.go
+++ b/internal/models/instances.go
@@ -1,5 +1,4 @@
-// Package bookkeeping implements reading and writing from the bookkeeping table
-package bookkeeping
+package models
import (
"database/sql/driver"
@@ -7,11 +6,15 @@ import (
"time"
)
-// Instance is a WissKI Instance inside the bookkeeping table.
+// InstanceTable is the name of the table the 'Instance' model is stored in.
+const InstanceTable = "distillery"
+
+// Instance is a WissKI Instance stored inside the sql database.
+//
// It does not represent a running instance; it does not perform any validation.
type Instance struct {
// NOTE: Modifying this struct requires a database migration.
- // This should nnever be done unless you know what you're doing.
+ // This should never be done unless you know what you're doing.
// Primary key for the instance
Pk uint `gorm:"column:pk;primaryKey"`
diff --git a/internal/wisski/snapshot.go b/internal/wisski/snapshot.go
index 43ac304..dd13767 100644
--- a/internal/wisski/snapshot.go
+++ b/internal/wisski/snapshot.go
@@ -8,8 +8,8 @@ import (
"strings"
"time"
- "github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
+ "github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/pkg/countwriter"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
@@ -82,7 +82,7 @@ type SnapshotDescription struct {
// Snapshot represents the result of generating a snapshot
type Snapshot struct {
Description SnapshotDescription
- Instance bookkeeping.Instance
+ Instance models.Instance
// Start and End Time of the snapshot
StartTime time.Time
diff --git a/pkg/environment/globals.go b/pkg/environment/globals.go
index 8581d9c..f334193 100644
--- a/pkg/environment/globals.go
+++ b/pkg/environment/globals.go
@@ -17,7 +17,7 @@ const ExecCommandError = 127
const DefaultFilePerm fs.FileMode = 0666
// DefaultDirPerm is the default mode to use for directories
-const DefaultDirPerm fs.FileMode = os.ModeDir & fs.ModePerm
+const DefaultDirPerm fs.FileMode = fs.ModeDir | fs.ModePerm
// IsExist checks if the provided error represents a 'does not exist' errror
func IsExist(err error) bool {
diff --git a/pkg/sqle/name.go b/pkg/sqle/name.go
index 53a223f..a323141 100644
--- a/pkg/sqle/name.go
+++ b/pkg/sqle/name.go
@@ -5,8 +5,13 @@ import (
"unicode"
)
-// IsSafeDatabaseName checks if a string is safe to be used as a database name
-func IsSafeDatabaseName(value string) bool {
+// IsSafeDatabaseSingleQuote checks if value can safely be put inside 's inside a database query
+func IsSafeDatabaseSingleQuote(value string) bool {
+ return !strings.ContainsAny(value, "'`") // TODO: This should be safer, but it's relatively controlled
+}
+
+// IsSafeDatabaseLiteral checks if a value is safe to be used as a database query literal
+func IsSafeDatabaseLiteral(value string) bool {
// the empty name is not allowed!
if len(value) == 0 {
return false
diff --git a/pkg/sqle/sqle.go b/pkg/sqle/sqle.go
index a8ffd1c..52bc717 100644
--- a/pkg/sqle/sqle.go
+++ b/pkg/sqle/sqle.go
@@ -4,6 +4,8 @@ import (
"github.com/feiin/sqlstring"
)
+// TODO: This is really unsafe and shouldn't be used at all.
+
// Format formats the provided query with the given parameters.
func Format(query string, params ...interface{}) string {
return sqlstring.Format(query, params...)