diff --git a/README.md b/README.md index 9bacc73..577c448 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,14 @@ Trello board: [Click me!](https://trello.com/b/6zWLE6Jm) ## Database Types - MariaDB -- MySQL (soon) -- MS-SQL (far future) +- MySQL +- MS-SQL -| Database Type | Config Type | -|-------------------|---------------------------| -| MariaDB | mariadb | +| Database Type | Config Type | +|---------------|-------------| +| MariaDB | mariadb | + | MySQL | mysql | + | MS-SQL | mssql | ## Storage types @@ -69,3 +71,38 @@ Trello board: [Click me!](https://trello.com/b/6zWLE6Jm) | fileshareName | string | The name of the Azure File Share | | storageAccountName | string | Name of your storage account | | storageAccountKey | string | Key for the storage account | + + +## Config Examples + +### config.json (Linux) +``` +{ + "foldersToBackup": [ + { + "backupName": "my-backup", + "folderPath": "/path/to/folder/to/backup", + "remoteStorageType": "remote-type", + "remoteTargetPath": "path/to", + "createLocalBackup": true, + "localTargetPath": "/path/for/local/backup" + } + ] +} +``` + +### config.json (Windows) +``` +{ + "foldersToBackup": [ + { + "backupName": "my-backup", + "folderPath": "D:\\Path\\To\\Folder\\To\\Backup", + "remoteStorageType": "remote-type", + "remoteTargetPath": "path/to", + "createLocalBackup": true, + "localTargetPath": "E:\\Path\\For\\Local Backup" + } + ] +} +``` \ No newline at end of file diff --git a/SQL/LogTypes.go b/SQL/LogTypes.go index 16f2ea0..40dbd47 100644 --- a/SQL/LogTypes.go +++ b/SQL/LogTypes.go @@ -1,10 +1,27 @@ package SQL +import "fmt" + type LogType int64 -const( - LogInfo LogType = 1 - LogWarning = 2 - LogError = 3 - LogFatal = 4 +const ( + LogInfo LogType = 1 + LogWarning = 2 + LogError = 3 + LogFatal = 4 ) + +func (e LogType) String() string { + switch e { + case LogInfo: + return "INFO" + case LogWarning: + return "WARNING" + case LogError: + return "ERROR" + case LogFatal: + return "FATAL" + default: + return fmt.Sprintf("%d", e) + } +} diff --git a/SQL/MSSQLConnector.go b/SQL/MSSQLConnector.go new file mode 100644 index 0000000..24ab2fe --- /dev/null +++ b/SQL/MSSQLConnector.go @@ -0,0 +1,144 @@ +package SQL + +import ( + "database/sql" + "fmt" + mssqlpkg "github.com/denisenkom/go-mssqldb" + "github.com/google/uuid" + "net/url" + "os" + "scabiosa/Logging" + "scabiosa/Tools" + "time" +) + +type MSSQLConnector struct { + Address string + Port uint16 + Database string + DbUser string + DbPassword string +} + +func GetMSSQLInstance(sqlConfig Tools.SQLConfig) MSSQLConnector { + var mssql MSSQLConnector + + mssql.Address = sqlConfig.SqlAddress + mssql.Port = sqlConfig.SqlPort + mssql.Database = sqlConfig.Database + mssql.DbUser = sqlConfig.DbUser + mssql.DbPassword = sqlConfig.DbPassword + + return mssql +} + +func (mssql MSSQLConnector) checkIfEventLogTableExist(db *sql.DB) bool { + rows, _ := db.Query("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'EventLog';") + if !rows.Next() { + return false + } + return true +} + +func (mssql MSSQLConnector) checkIfBackupTableExist(db *sql.DB) bool { + rows, _ := db.Query("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'Backups';") + if !rows.Next() { + return false + } + return true +} + +func (mssql MSSQLConnector) checkIfBackupEntryExist(db *sql.DB, backupName string, hostname string) bool { + query := fmt.Sprintf("SELECT * FROM dbo.Backups WHERE Hostname = '%s' AND BackupName = '%s'", hostname, backupName) + rows, _ := db.Query(query) + if !rows.Next() { + return false + } + return true +} + +func createMSSQLConnection(mssql MSSQLConnector) *sql.DB { + logger := Logging.DetailedLogger("MS-SQL", "createConnection") + + query := url.Values{} + query.Add("app name", "scabiosa") + query.Add("database", "scabiosa-test") + + sqlSettings := &url.URL{ + Scheme: "sqlserver", + User: url.UserPassword(mssql.DbUser, mssql.DbPassword), + Host: fmt.Sprintf("%s:%d", mssql.Address, mssql.Port), + RawQuery: query.Encode(), + } + + connector, err := mssqlpkg.NewConnector(sqlSettings.String()) + if err != nil { + logger.Fatal(err) + } + + connector.SessionInitSQL = "SET ANSI_NULLS ON" + + db := sql.OpenDB(connector) + + return db +} + +func (mssql MSSQLConnector) createDefaultTables() { + logger := Logging.DetailedLogger("MS-SQL", "createDefaultTables") + + eventLogSQL := "create table dbo.EventLog(UUID text null, LogType VARCHAR(20) NOT NULL CHECK (LogType IN('INFO', 'WARNING', 'ERROR', 'FATAL')), Hostname varchar(256) null, BackupName varchar(256) null, Stage VARCHAR(20) NOT NULL CHECK (Stage IN('COMPRESS', 'UPLOAD', 'DELETE TMP')), RemoteStorage VARCHAR(20) NOT NULL CHECK (RemoteStorage IN('AZURE-FILE', 'AZURE-BLOB', 'NONE')), Description text null, Timestamp datetime null);" + backupSQL := "create table dbo.Backups(UUID text null, Hostname varchar(256) null, BackupName varchar(256) null, LastBackup datetime null, LocalBackup tinyint null, FilePath varchar(256) null, RemoteStorage VARCHAR(20) NOT NULL CHECK (RemoteStorage IN('AZURE-FILE', 'AZURE-BLOB', 'NONE')), RemotePath varchar(256) null, LocalPath varchar(256) null);" + + db := createMSSQLConnection(mssql) + + if !mssql.checkIfBackupTableExist(db) { + _, err := db.Exec(backupSQL) + if err != nil { + logger.Fatal(err) + } + } + + if !mssql.checkIfEventLogTableExist(db) { + _, err := db.Exec(eventLogSQL) + if err != nil { + logger.Fatal(err) + } + } + + _ = db.Close() +} +func (mssql MSSQLConnector) newLogEntry(uuid uuid.UUID, logType LogType, backupName string, stage SQLStage, storageType RemoteStorageType, description string, timestamp time.Time) { + logger := Logging.DetailedLogger("MS-SQL", "newLogEntry") + db := createMSSQLConnection(mssql) + + hostname, _ := os.Hostname() + query := fmt.Sprintf("INSERT INTO dbo.EventLog VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", uuid.String(), logType.String(), hostname, backupName, stage.String(), storageType.String(), description, timestamp.Format("2006-01-02 15:04:05.999")) + _, err := db.Query(query) + if err != nil { + logger.Fatal(err) + } +} +func (mssql MSSQLConnector) newBackupEntry(backupName string, lastBackup time.Time, localBackup bool, filePath string, storageType RemoteStorageType, remotePath string, localPath string) { + logger := Logging.DetailedLogger("MS-SQL", "newBackupEntry") + db := createMSSQLConnection(mssql) + + hostname, _ := os.Hostname() + var localBackupInt uint8 + if localBackup { + localBackupInt = 1 + } + + if mssql.checkIfBackupEntryExist(db, backupName, hostname) { + queryUpdate := fmt.Sprintf("UPDATE dbo.Backups SET Lastbackup = '%s', LocalBackup = %d, RemoteStorage = '%s', RemotePath = '%s', LocalPath = '%s' WHERE Hostname = '%s' AND BackupName = '%s'", lastBackup.Format("2006-01-02 15:04:05.999"), localBackupInt, storageType.String(), remotePath, localPath, hostname, backupName) + _, err := db.Query(queryUpdate) + if err != nil { + logger.Fatal(err) + } + } else { + queryInsert := fmt.Sprintf("INSERT INTO dbo.Backups VALUES ('%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s')", uuid.New(), hostname, backupName, lastBackup.Format("2006-01-02 15:04:05.999"), localBackupInt, filePath, storageType.String(), remotePath, localPath) + _, err := db.Query(queryInsert) + if err != nil { + logger.Fatal(err) + } + } +} diff --git a/SQL/MariaDBConnector.go b/SQL/MariaDBConnector.go index 55b14c9..adc50a2 100644 --- a/SQL/MariaDBConnector.go +++ b/SQL/MariaDBConnector.go @@ -31,7 +31,7 @@ func GetMariaDBInstance(sqlConfig Tools.SQLConfig) MariaDBConnector { return mariadb } -func checkIfEventLogTableExist(db *sql.DB, mariadb MariaDBConnector) bool { +func (mariadb MariaDBConnector) checkIfEventLogTableExist(db *sql.DB) bool { rows, _ := db.Query("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'EventLog';", mariadb.Database) if !rows.Next() { return false @@ -39,7 +39,7 @@ func checkIfEventLogTableExist(db *sql.DB, mariadb MariaDBConnector) bool { return true } -func checkIfBackupTableExist(db *sql.DB, mariadb MariaDBConnector) bool { +func (mariadb MariaDBConnector) checkIfBackupTableExist(db *sql.DB) bool { rows, _ := db.Query("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'Backups';", mariadb.Database) if !rows.Next() { return false @@ -47,7 +47,7 @@ func checkIfBackupTableExist(db *sql.DB, mariadb MariaDBConnector) bool { return true } -func checkIfBackupEntryExist(db *sql.DB, mariadb MariaDBConnector, backupName string, hostname string) bool { +func (mariadb MariaDBConnector) checkIfBackupEntryExist(db *sql.DB, backupName string, hostname string) bool { rows, _ := db.Query("SELECT * FROM `"+mariadb.Database+"`.Backups WHERE Hostname = ? AND BackupName = ?;", hostname, backupName) if !rows.Next() { return false @@ -72,14 +72,14 @@ func (mariadb MariaDBConnector) createDefaultTables() { db := createMariaDBConnection(mariadb) - if !checkIfBackupTableExist(db, mariadb) { + if !mariadb.checkIfBackupTableExist(db) { _, err := db.Exec(backupSQL) if err != nil { logger.Fatal(err) } } - if !checkIfEventLogTableExist(db, mariadb) { + if !mariadb.checkIfEventLogTableExist(db) { _, err := db.Exec(eventLogSQL) if err != nil { logger.Fatal(err) @@ -108,7 +108,7 @@ func (mariadb MariaDBConnector) newBackupEntry(backupName string, lastBackup tim hostname, _ := os.Hostname() - if checkIfBackupEntryExist(db, mariadb, backupName, hostname) { + if mariadb.checkIfBackupEntryExist(db, backupName, hostname) { _, err := db.Query("UPDATE `"+mariadb.Database+"`.Backups SET LastBackup = ?, LocalBackup = ?, RemoteStorage = ?, RemotePath = ?, LocalPath = ? WHERE Hostname = ? AND BackupName = ?;", lastBackup, localBackup, strconv.FormatInt(int64(storageType), 10), remotePath, localPath, hostname, backupName) if err != nil { logger.Fatal(err) diff --git a/SQL/RemoteStorage.go b/SQL/RemoteStorage.go index dd835c0..7c3ae82 100644 --- a/SQL/RemoteStorage.go +++ b/SQL/RemoteStorage.go @@ -1,9 +1,24 @@ package SQL +import "fmt" + type RemoteStorageType int64 -const( +const ( REMOTE_AZURE_FILE = 1 REMOTE_AZURE_BLOB = 2 - REMOTE_NONE = 3 -) \ No newline at end of file + REMOTE_NONE = 3 +) + +func (e RemoteStorageType) String() string { + switch e { + case REMOTE_AZURE_FILE: + return "AZURE-FILE" + case REMOTE_AZURE_BLOB: + return "AZURE-BLOB" + case REMOTE_NONE: + return "NONE" + default: + return fmt.Sprintf("%d", e) + } +} diff --git a/SQL/SQLInterface.go b/SQL/SQLInterface.go index fd03e1c..393a8ba 100644 --- a/SQL/SQLInterface.go +++ b/SQL/SQLInterface.go @@ -45,6 +45,14 @@ func GetSQLInstance() SQLService { { return GetMariaDBInstance(sqlConfig) } + case "mysql": + { + return GetMariaDBInstance(sqlConfig) + } + case "mssql": + { + return GetMSSQLInstance(sqlConfig) + } } return nil diff --git a/SQL/SQLStages.go b/SQL/SQLStages.go index 36c7441..e71c933 100644 --- a/SQL/SQLStages.go +++ b/SQL/SQLStages.go @@ -1,9 +1,24 @@ package SQL +import "fmt" + type SQLStage int64 -const( - SQLStage_Compress = 1 - SQLStage_Upload = 2 +const ( + SQLStage_Compress = 1 + SQLStage_Upload = 2 SQLStage_DeleteTmp = 3 ) + +func (e SQLStage) String() string { + switch e { + case SQLStage_Compress: + return "COMPRESS" + case SQLStage_Upload: + return "UPLOAD" + case SQLStage_DeleteTmp: + return "DELETE TMP" + default: + return fmt.Sprintf("%d", e) + } +} diff --git a/go.mod b/go.mod index 230aab4..12aac11 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,9 @@ require ( github.com/Azure/azure-pipeline-go v0.2.1 // indirect github.com/VividCortex/ewma v1.1.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/denisenkom/go-mssqldb v0.11.0 // indirect github.com/fatih/color v1.10.0 // indirect + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149 // indirect github.com/mattn/go-isatty v0.0.12 // indirect @@ -25,6 +27,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect golang.org/x/text v0.3.0 // indirect diff --git a/go.sum b/go.sum index feb7c0b..e5ebb45 100644 --- a/go.sum +++ b/go.sum @@ -11,10 +11,14 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= +github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -49,6 +53,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=