Task/DB-Migration: Add Key-Value to SQL Migration functionality. (#3380)

* feat(go): add db package;

* feat(go): add jobs table;

* feat(go): add schema migration facade;

* refactor(go): use custom key type to avoid collisions;
This commit is contained in:
Vishal Dalwadi 2025-04-12 03:07:57 -07:00 committed by GitHub
parent 02af8f6e5c
commit 7f40371ffd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 533 additions and 31 deletions

View file

@ -102,6 +102,35 @@ const (
var dbMutex sync.RWMutex
var Tables = []string{
NETWORKS_TABLE_NAME,
NODES_TABLE_NAME,
CERTS_TABLE_NAME,
DELETED_NODES_TABLE_NAME,
USERS_TABLE_NAME,
DNS_TABLE_NAME,
EXT_CLIENT_TABLE_NAME,
PEERS_TABLE_NAME,
SERVERCONF_TABLE_NAME,
SERVER_UUID_TABLE_NAME,
GENERATED_TABLE_NAME,
NODE_ACLS_TABLE_NAME,
SSO_STATE_CACHE,
METRICS_TABLE_NAME,
NETWORK_USER_TABLE_NAME,
USER_GROUPS_TABLE_NAME,
CACHE_TABLE_NAME,
HOSTS_TABLE_NAME,
ENROLLMENT_KEYS_TABLE_NAME,
HOST_ACTIONS_TABLE_NAME,
PENDING_USERS_TABLE_NAME,
USER_PERMISSIONS_TABLE_NAME,
USER_INVITES_TABLE_NAME,
TAG_TABLE_NAME,
ACLS_TABLE_NAME,
PEER_ACK_TABLE,
}
func getCurrentDB() map[string]interface{} {
switch servercfg.GetDB() {
case "rqlite":
@ -135,32 +164,9 @@ func InitializeDatabase() error {
}
func createTables() {
CreateTable(NETWORKS_TABLE_NAME)
CreateTable(NODES_TABLE_NAME)
CreateTable(CERTS_TABLE_NAME)
CreateTable(DELETED_NODES_TABLE_NAME)
CreateTable(USERS_TABLE_NAME)
CreateTable(DNS_TABLE_NAME)
CreateTable(EXT_CLIENT_TABLE_NAME)
CreateTable(PEERS_TABLE_NAME)
CreateTable(SERVERCONF_TABLE_NAME)
CreateTable(SERVER_UUID_TABLE_NAME)
CreateTable(GENERATED_TABLE_NAME)
CreateTable(NODE_ACLS_TABLE_NAME)
CreateTable(SSO_STATE_CACHE)
CreateTable(METRICS_TABLE_NAME)
CreateTable(NETWORK_USER_TABLE_NAME)
CreateTable(USER_GROUPS_TABLE_NAME)
CreateTable(CACHE_TABLE_NAME)
CreateTable(HOSTS_TABLE_NAME)
CreateTable(ENROLLMENT_KEYS_TABLE_NAME)
CreateTable(HOST_ACTIONS_TABLE_NAME)
CreateTable(PENDING_USERS_TABLE_NAME)
CreateTable(USER_PERMISSIONS_TABLE_NAME)
CreateTable(USER_INVITES_TABLE_NAME)
CreateTable(TAG_TABLE_NAME)
CreateTable(ACLS_TABLE_NAME)
CreateTable(PEER_ACK_TABLE)
for _, table := range Tables {
_ = CreateTable(table)
}
}
func CreateTable(tableName string) error {

28
db/connector.go Normal file
View file

@ -0,0 +1,28 @@
package db
import (
"errors"
"github.com/gravitl/netmaker/servercfg"
"gorm.io/gorm"
)
var ErrUnsupportedDB = errors.New("unsupported db type")
// connector helps connect to a database,
// along with any initializations required.
type connector interface {
connect() (*gorm.DB, error)
}
// newConnector detects the database being
// used and returns the corresponding connector.
func newConnector() (connector, error) {
switch servercfg.GetDB() {
case "sqlite":
return &sqliteConnector{}, nil
case "postgres":
return &postgresConnector{}, nil
default:
return nil, ErrUnsupportedDB
}
}

99
db/db.go Normal file
View file

@ -0,0 +1,99 @@
package db
import (
"context"
"errors"
"gorm.io/gorm"
"net/http"
"time"
)
type ctxKey string
const dbCtxKey ctxKey = "db"
var db *gorm.DB
var ErrDBNotFound = errors.New("no db instance in context")
// InitializeDB initializes a connection to the
// database (if not already done) and ensures it
// has the latest schema.
func InitializeDB(models ...interface{}) error {
if db != nil {
return nil
}
connector, err := newConnector()
if err != nil {
return err
}
// DB / LIFE ADVICE: try 5 times before giving up.
for i := 0; i < 5; i++ {
db, err = connector.connect()
if err == nil {
break
}
// wait 2s if you have the time.
time.Sleep(2 * time.Second)
}
if err != nil {
return err
}
return db.AutoMigrate(models...)
}
// WithContext returns a new context with the db
// connection instance.
//
// Ensure InitializeDB has been called before using
// this function.
//
// To extract the db connection use the FromContext
// function.
func WithContext(ctx context.Context) context.Context {
return context.WithValue(ctx, dbCtxKey, db)
}
// Middleware to auto-inject the db connection instance
// in a request's context.
//
// Ensure InitializeDB has been called before using this
// middleware.
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r.WithContext(WithContext(r.Context())))
})
}
// FromContext extracts the db connection instance from
// the given context.
//
// The function panics, if a connection does not exist.
func FromContext(ctx context.Context) *gorm.DB {
db, ok := ctx.Value(dbCtxKey).(*gorm.DB)
if !ok {
panic(ErrDBNotFound)
}
return db
}
// BeginTx returns a context with a new transaction.
// If the context already has a db connection instance,
// it uses that instance. Otherwise, it uses the
// connection initialized in the package.
//
// Ensure InitializeDB has been called before using
// this function.
func BeginTx(ctx context.Context) context.Context {
dbInCtx, ok := ctx.Value(dbCtxKey).(*gorm.DB)
if !ok {
return context.WithValue(ctx, dbCtxKey, db.Begin())
}
return context.WithValue(ctx, dbCtxKey, dbInCtx.Begin())
}

49
db/postgres.go Normal file
View file

@ -0,0 +1,49 @@
package db
import (
"fmt"
"github.com/gravitl/netmaker/servercfg"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// postgresConnector for initializing and
// connecting to a postgres database.
type postgresConnector struct{}
// postgresConnector.connect connects and
// initializes a connection to postgres.
func (pg *postgresConnector) connect() (*gorm.DB, error) {
pgConf := servercfg.GetSQLConf()
dsn := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s connect_timeout=5",
pgConf.Host,
pgConf.Port,
pgConf.Username,
pgConf.Password,
pgConf.DB,
pgConf.SSLMode,
)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return nil, err
}
// ensure netmaker_v1 schema exists.
err = db.Exec("CREATE SCHEMA IF NOT EXISTS netmaker_v1").Error
if err != nil {
return nil, err
}
// set the netmaker_v1 schema as the default schema.
err = db.Exec("SET search_path TO netmaker_v1").Error
if err != nil {
return nil, err
}
return db, nil
}

54
db/sqlite.go Normal file
View file

@ -0,0 +1,54 @@
package db
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"os"
"path/filepath"
)
// sqliteConnector for initializing and
// connecting to a sqlite database.
type sqliteConnector struct{}
// sqliteConnector.connect connects and
// initializes a connection to sqlite.
func (s *sqliteConnector) connect() (*gorm.DB, error) {
// ensure data dir exists.
_, err := os.Stat("data")
if err != nil {
if os.IsNotExist(err) {
err = os.Mkdir("data", 0700)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
dbFilePath := filepath.Join("data", "netmaker_v1.db")
// ensure netmaker_v1.db exists.
_, err = os.Stat(dbFilePath)
if err != nil {
if os.IsNotExist(err) {
file, err := os.Create(dbFilePath)
if err != nil {
return nil, err
}
err = file.Close()
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
return gorm.Open(sqlite.Open(dbFilePath), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
}

21
go.mod
View file

@ -1,6 +1,8 @@
module github.com/gravitl/netmaker
go 1.23
go 1.23.0
toolchain go1.23.7
require (
github.com/blang/semver v3.5.1+incompatible
@ -18,11 +20,11 @@ require (
github.com/stretchr/testify v1.10.0
github.com/txn2/txeh v1.5.5
go.uber.org/automaxprocs v1.6.0
golang.org/x/crypto v0.32.0
golang.org/x/crypto v0.36.0
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.24.0
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
gopkg.in/yaml.v3 v3.0.1
)
@ -53,11 +55,20 @@ require (
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.2 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/seancfoley/bintree v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gorm.io/driver/postgres v1.5.11 // indirect
gorm.io/driver/sqlite v1.5.7 // indirect
gorm.io/gorm v1.25.12 // indirect
)
require (
@ -69,5 +80,5 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sync v0.12.0 // indirect
)

28
go.sum
View file

@ -49,8 +49,21 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@ -90,6 +103,7 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@ -103,6 +117,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -121,6 +137,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -131,6 +149,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -144,6 +164,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@ -161,3 +183,9 @@ gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

183
migrate/migrate_schema.go Normal file
View file

@ -0,0 +1,183 @@
package migrate
import (
"context"
"errors"
"fmt"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/db"
"github.com/gravitl/netmaker/schema"
"github.com/gravitl/netmaker/servercfg"
"gorm.io/gorm"
"os"
"path/filepath"
)
// ToSQLSchema migrates the data from key-value
// db to sql db.
//
// This function archives the old data and does not
// delete it.
//
// Based on the db server, the archival is done in the
// following way:
//
// 1. Sqlite: Moves the old data to a
// netmaker_archive.db file.
//
// 2. Postgres: Moves the data to a netmaker_archive
// schema within the same database.
func ToSQLSchema() error {
// initialize sql schema db.
err := db.InitializeDB(schema.ListModels()...)
if err != nil {
return err
}
// migrate, if not done already.
err = migrate()
if err != nil {
return err
}
// archive key-value schema db, if not done already.
// ignore errors.
_ = archive()
return nil
}
func migrate() error {
// begin a new transaction.
dbctx := db.BeginTx(context.TODO())
commit := false
defer func() {
if commit {
db.FromContext(dbctx).Commit()
} else {
db.FromContext(dbctx).Rollback()
}
}()
// check if migrated already.
migrationJob := &schema.Job{
ID: "migration-v1.0.0",
}
err := migrationJob.Get(dbctx)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
// initialize key-value schema db.
err := database.InitializeDatabase()
if err != nil {
return err
}
defer database.CloseDB()
// migrate.
// TODO: add migration code.
// mark migration job completed.
err = migrationJob.Create(dbctx)
if err != nil {
return err
}
commit = true
}
return nil
}
func archive() error {
dbServer := servercfg.GetDB()
if dbServer != "sqlite" && dbServer != "postgres" {
return nil
}
// begin a new transaction.
dbctx := db.BeginTx(context.TODO())
commit := false
defer func() {
if commit {
db.FromContext(dbctx).Commit()
} else {
db.FromContext(dbctx).Rollback()
}
}()
// check if key-value schema db archived already.
archivalJob := &schema.Job{
ID: "archival-v1.0.0",
}
err := archivalJob.Get(dbctx)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
// archive.
switch dbServer {
case "sqlite":
err = sqliteArchiveOldData()
default:
err = pgArchiveOldData()
}
if err != nil {
return err
}
// mark archival job completed.
err = archivalJob.Create(dbctx)
if err != nil {
return err
}
commit = true
} else {
// remove the residual
if dbServer == "sqlite" {
_ = os.Remove(filepath.Join("data", "netmaker.db"))
}
}
return nil
}
func sqliteArchiveOldData() error {
oldDBFilePath := filepath.Join("data", "netmaker.db")
archiveDBFilePath := filepath.Join("data", "netmaker_archive.db")
// check if netmaker_archive.db exist.
_, err := os.Stat(archiveDBFilePath)
if err == nil {
return nil
} else if !os.IsNotExist(err) {
return err
}
// rename old db file to netmaker_archive.db.
return os.Rename(oldDBFilePath, archiveDBFilePath)
}
func pgArchiveOldData() error {
_, err := database.PGDB.Exec("CREATE SCHEMA IF NOT EXISTS netmaker_archive")
if err != nil {
return err
}
for _, table := range database.Tables {
_, err := database.PGDB.Exec(
fmt.Sprintf(
"ALTER TABLE public.%s SET SCHEMA netmaker_archive",
table,
),
)
if err != nil {
return err
}
}
return nil
}

36
schema/jobs.go Normal file
View file

@ -0,0 +1,36 @@
package schema
import (
"context"
"github.com/gravitl/netmaker/db"
"time"
)
// Job represents a task that netmaker server
// wants to do.
//
// Ideally, a jobs table should have details
// about its type, status, who initiated it,
// etc. But, for now, the table only contains
// records of jobs that have been done, so
// that it is easier to prevent a task from
// being executed again.
type Job struct {
ID string `gorm:"id;primary_key"`
CreatedAt time.Time `gorm:"created_at"`
}
// TableName returns the name of the jobs table.
func (j *Job) TableName() string {
return "jobs"
}
// Create creates a job record in the jobs table.
func (j *Job) Create(ctx context.Context) error {
return db.FromContext(ctx).Table(j.TableName()).Create(j).Error
}
// Get returns a job record with the given Job.ID.
func (j *Job) Get(ctx context.Context) error {
return db.FromContext(ctx).Table(j.TableName()).Where("id = ?", j.ID).First(j).Error
}

8
schema/models.go Normal file
View file

@ -0,0 +1,8 @@
package schema
// ListModels lists all the models in this schema.
func ListModels() []interface{} {
return []interface{}{
&Job{},
}
}