SQL + sqlite in memory drivers ok | Index.gohtml rendered at AdminPath
- commit
- c9f4eea3ab75816c9b1fb72d7dd043789e16b142
- author
- bnema · · 2023-09-14 15:05 UTC
- parent
- accf0b9
- signature
- unsigned
7 files · +283 -21
M go.mod +21 -0
| @@ -4,25 +4,46 @@ go 1.21.1 | |||
| 4 | 4 | ||
| 5 | 5 | require ( | |
| 6 | 6 | github.com/PuerkitoBio/goquery v1.8.1 | |
| 7 | + | github.com/docker/docker v24.0.6+incompatible | |
| 7 | 8 | github.com/labstack/echo-contrib v0.15.0 | |
| 8 | 9 | github.com/labstack/echo/v4 v4.11.1 | |
| 10 | + | github.com/mattn/go-sqlite3 v1.14.17 | |
| 9 | 11 | gopkg.in/yaml.v3 v3.0.1 | |
| 10 | 12 | ) | |
| 11 | 13 | ||
| 12 | 14 | require ( | |
| 15 | + | github.com/Microsoft/go-winio v0.6.1 // indirect | |
| 13 | 16 | github.com/andybalholm/cascadia v1.3.1 // indirect | |
| 17 | + | github.com/containerd/containerd v1.7.6 // indirect | |
| 18 | + | github.com/docker/distribution v2.8.2+incompatible // indirect | |
| 19 | + | github.com/docker/go-connections v0.4.0 // indirect | |
| 20 | + | github.com/docker/go-units v0.5.0 // indirect | |
| 21 | + | github.com/gogo/protobuf v1.3.2 // indirect | |
| 14 | 22 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | |
| 15 | 23 | github.com/gorilla/context v1.1.1 // indirect | |
| 16 | 24 | github.com/gorilla/securecookie v1.1.1 // indirect | |
| 17 | 25 | github.com/gorilla/sessions v1.2.1 // indirect | |
| 26 | + | github.com/klauspost/compress v1.16.7 // indirect | |
| 18 | 27 | github.com/labstack/gommon v0.4.0 // indirect | |
| 19 | 28 | github.com/mattn/go-colorable v0.1.13 // indirect | |
| 20 | 29 | github.com/mattn/go-isatty v0.0.19 // indirect | |
| 30 | + | github.com/moby/patternmatcher v0.6.0 // indirect | |
| 31 | + | github.com/moby/sys/sequential v0.5.0 // indirect | |
| 32 | + | github.com/moby/term v0.5.0 // indirect | |
| 33 | + | github.com/morikuni/aec v1.0.0 // indirect | |
| 34 | + | github.com/opencontainers/go-digest v1.0.0 // indirect | |
| 35 | + | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect | |
| 36 | + | github.com/opencontainers/runc v1.1.9 // indirect | |
| 37 | + | github.com/pkg/errors v0.9.1 // indirect | |
| 38 | + | github.com/sirupsen/logrus v1.9.3 // indirect | |
| 21 | 39 | github.com/valyala/bytebufferpool v1.0.0 // indirect | |
| 22 | 40 | github.com/valyala/fasttemplate v1.2.2 // indirect | |
| 23 | 41 | golang.org/x/crypto v0.11.0 // indirect | |
| 42 | + | golang.org/x/mod v0.9.0 // indirect | |
| 24 | 43 | golang.org/x/net v0.12.0 // indirect | |
| 25 | 44 | golang.org/x/sys v0.10.0 // indirect | |
| 26 | 45 | golang.org/x/text v0.11.0 // indirect | |
| 27 | 46 | golang.org/x/time v0.3.0 // indirect | |
| 47 | + | golang.org/x/tools v0.7.0 // indirect | |
| 48 | + | gotest.tools/v3 v3.5.0 // indirect | |
| 28 | 49 | ) | |
| @@ -4,25 +4,46 @@ go 1.21.1 | |||||
| 4 | 4 | ||||
| 5 | require ( | 5 | require ( | ||
| 6 | github.com/PuerkitoBio/goquery v1.8.1 | 6 | github.com/PuerkitoBio/goquery v1.8.1 | ||
| 7 | + | github.com/docker/docker v24.0.6+incompatible | |||
| 7 | github.com/labstack/echo-contrib v0.15.0 | 8 | github.com/labstack/echo-contrib v0.15.0 | ||
| 8 | github.com/labstack/echo/v4 v4.11.1 | 9 | github.com/labstack/echo/v4 v4.11.1 | ||
| 10 | + | github.com/mattn/go-sqlite3 v1.14.17 | |||
| 9 | gopkg.in/yaml.v3 v3.0.1 | 11 | gopkg.in/yaml.v3 v3.0.1 | ||
| 10 | ) | 12 | ) | ||
| 11 | 13 | ||||
| 12 | require ( | 14 | require ( | ||
| 15 | + | github.com/Microsoft/go-winio v0.6.1 // indirect | |||
| 13 | github.com/andybalholm/cascadia v1.3.1 // indirect | 16 | github.com/andybalholm/cascadia v1.3.1 // indirect | ||
| 17 | + | github.com/containerd/containerd v1.7.6 // indirect | |||
| 18 | + | github.com/docker/distribution v2.8.2+incompatible // indirect | |||
| 19 | + | github.com/docker/go-connections v0.4.0 // indirect | |||
| 20 | + | github.com/docker/go-units v0.5.0 // indirect | |||
| 21 | + | github.com/gogo/protobuf v1.3.2 // indirect | |||
| 14 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | 22 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | ||
| 15 | github.com/gorilla/context v1.1.1 // indirect | 23 | github.com/gorilla/context v1.1.1 // indirect | ||
| 16 | github.com/gorilla/securecookie v1.1.1 // indirect | 24 | github.com/gorilla/securecookie v1.1.1 // indirect | ||
| 17 | github.com/gorilla/sessions v1.2.1 // indirect | 25 | github.com/gorilla/sessions v1.2.1 // indirect | ||
| 26 | + | github.com/klauspost/compress v1.16.7 // indirect | |||
| 18 | github.com/labstack/gommon v0.4.0 // indirect | 27 | github.com/labstack/gommon v0.4.0 // indirect | ||
| 19 | github.com/mattn/go-colorable v0.1.13 // indirect | 28 | github.com/mattn/go-colorable v0.1.13 // indirect | ||
| 20 | github.com/mattn/go-isatty v0.0.19 // indirect | 29 | github.com/mattn/go-isatty v0.0.19 // indirect | ||
| 30 | + | github.com/moby/patternmatcher v0.6.0 // indirect | |||
| 31 | + | github.com/moby/sys/sequential v0.5.0 // indirect | |||
| 32 | + | github.com/moby/term v0.5.0 // indirect | |||
| 33 | + | github.com/morikuni/aec v1.0.0 // indirect | |||
| 34 | + | github.com/opencontainers/go-digest v1.0.0 // indirect | |||
| 35 | + | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect | |||
| 36 | + | github.com/opencontainers/runc v1.1.9 // indirect | |||
| 37 | + | github.com/pkg/errors v0.9.1 // indirect | |||
| 38 | + | github.com/sirupsen/logrus v1.9.3 // indirect | |||
| 21 | github.com/valyala/bytebufferpool v1.0.0 // indirect | 39 | github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
| 22 | github.com/valyala/fasttemplate v1.2.2 // indirect | 40 | github.com/valyala/fasttemplate v1.2.2 // indirect | ||
| 23 | golang.org/x/crypto v0.11.0 // indirect | 41 | golang.org/x/crypto v0.11.0 // indirect | ||
| 42 | + | golang.org/x/mod v0.9.0 // indirect | |||
| 24 | golang.org/x/net v0.12.0 // indirect | 43 | golang.org/x/net v0.12.0 // indirect | ||
| 25 | golang.org/x/sys v0.10.0 // indirect | 44 | golang.org/x/sys v0.10.0 // indirect | ||
| 26 | golang.org/x/text v0.11.0 // indirect | 45 | golang.org/x/text v0.11.0 // indirect | ||
| 27 | golang.org/x/time v0.3.0 // indirect | 46 | golang.org/x/time v0.3.0 // indirect | ||
| 47 | + | golang.org/x/tools v0.7.0 // indirect | |||
| 48 | + | gotest.tools/v3 v3.5.0 // indirect | |||
| 28 | ) | 49 | ) | ||
M go.sum +75 -2 generated file: collapsed
M internal/app/app.go +29 -4
| @@ -2,6 +2,7 @@ package app | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "io/fs" | |
| 5 | + | "path/filepath" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/bnema/gordon/internal/gotemplate" | |
| 7 | 8 | "github.com/bnema/gordon/internal/webui" | |
| @@ -10,11 +11,17 @@ import ( | |||
| 10 | 11 | const ( | |
| 11 | 12 | BuildVersion = "0.0.2" | |
| 12 | 13 | BuildDir = "tmp" | |
| 14 | + | DBDir = "tmp/data" | |
| 15 | + | DBFilename = "sqlite.db" | |
| 13 | 16 | // Those configs should be read from a config file | |
| 14 | - | HttpPort = 1323 | |
| 15 | - | TopDomain = "example.com" | |
| 16 | - | SubDomain = "gordon" | |
| 17 | - | AdminPath = "/admin" | |
| 17 | + | HttpPort = 1323 | |
| 18 | + | TopDomain = "example.com" | |
| 19 | + | SubDomain = "gordon" | |
| 20 | + | AdminPath = "/admin" | |
| 21 | + | DockerSock = "/var/run/docker.sock" | |
| 22 | + | // If enabled replace all docker commands by podman | |
| 23 | + | PodmanEnable = false | |
| 24 | + | PodmanSock = "/run/user/1000/podman/podman.sock" | |
| 18 | 25 | ) | |
| 19 | 26 | ||
| 20 | 27 | type App struct { | |
| @@ -22,9 +29,14 @@ type App struct { | |||
| 22 | 29 | PublicFS fs.FS | |
| 23 | 30 | BuildVersion string | |
| 24 | 31 | BuildDir string | |
| 32 | + | DBDir string | |
| 33 | + | DBFilename string | |
| 25 | 34 | TopDomain string | |
| 26 | 35 | SubDomain string | |
| 27 | 36 | AdminPath string | |
| 37 | + | DockerSock string | |
| 38 | + | PodmanEnable bool | |
| 39 | + | PodmanSock string | |
| 28 | 40 | HttpPort int16 | |
| 29 | 41 | } | |
| 30 | 42 | ||
| @@ -34,9 +46,22 @@ func NewApp() *App { | |||
| 34 | 46 | PublicFS: webui.PublicFS, | |
| 35 | 47 | BuildVersion: BuildVersion, | |
| 36 | 48 | BuildDir: BuildDir, | |
| 49 | + | DBDir: DBDir, | |
| 50 | + | DBFilename: DBFilename, | |
| 37 | 51 | TopDomain: TopDomain, | |
| 38 | 52 | SubDomain: SubDomain, | |
| 39 | 53 | AdminPath: AdminPath, | |
| 40 | 54 | HttpPort: HttpPort, | |
| 41 | 55 | } | |
| 42 | 56 | } | |
| 57 | + | ||
| 58 | + | func NewDockerClient() *App { | |
| 59 | + | return &App{ | |
| 60 | + | DockerSock: DockerSock, | |
| 61 | + | PodmanEnable: PodmanEnable, | |
| 62 | + | PodmanSock: PodmanSock, | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | func (a *App) GetDBFilePath() string { | |
| 66 | + | return filepath.Join(a.DBDir, a.DBFilename) | |
| 67 | + | } | |
| @@ -2,6 +2,7 @@ package app | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "io/fs" | 4 | "io/fs" | ||
| 5 | + | "path/filepath" | |||
| 5 | 6 | ||||
| 6 | "github.com/bnema/gordon/internal/gotemplate" | 7 | "github.com/bnema/gordon/internal/gotemplate" | ||
| 7 | "github.com/bnema/gordon/internal/webui" | 8 | "github.com/bnema/gordon/internal/webui" | ||
| @@ -10,11 +11,17 @@ import ( | |||||
| 10 | const ( | 11 | const ( | ||
| 11 | BuildVersion = "0.0.2" | 12 | BuildVersion = "0.0.2" | ||
| 12 | BuildDir = "tmp" | 13 | BuildDir = "tmp" | ||
| 14 | + | DBDir = "tmp/data" | |||
| 15 | + | DBFilename = "sqlite.db" | |||
| 13 | // Those configs should be read from a config file | 16 | // Those configs should be read from a config file | ||
| 14 | - | HttpPort = 1323 | 17 | + | HttpPort = 1323 |
| 15 | - | TopDomain = "example.com" | 18 | + | TopDomain = "example.com" |
| 16 | - | SubDomain = "gordon" | 19 | + | SubDomain = "gordon" |
| 17 | - | AdminPath = "/admin" | 20 | + | AdminPath = "/admin" |
| 21 | + | DockerSock = "/var/run/docker.sock" | |||
| 22 | + | // If enabled replace all docker commands by podman | |||
| 23 | + | PodmanEnable = false | |||
| 24 | + | PodmanSock = "/run/user/1000/podman/podman.sock" | |||
| 18 | ) | 25 | ) | ||
| 19 | 26 | ||||
| 20 | type App struct { | 27 | type App struct { | ||
| @@ -22,9 +29,14 @@ type App struct { | |||||
| 22 | PublicFS fs.FS | 29 | PublicFS fs.FS | ||
| 23 | BuildVersion string | 30 | BuildVersion string | ||
| 24 | BuildDir string | 31 | BuildDir string | ||
| 32 | + | DBDir string | |||
| 33 | + | DBFilename string | |||
| 25 | TopDomain string | 34 | TopDomain string | ||
| 26 | SubDomain string | 35 | SubDomain string | ||
| 27 | AdminPath string | 36 | AdminPath string | ||
| 37 | + | DockerSock string | |||
| 38 | + | PodmanEnable bool | |||
| 39 | + | PodmanSock string | |||
| 28 | HttpPort int16 | 40 | HttpPort int16 | ||
| 29 | } | 41 | } | ||
| 30 | 42 | ||||
| @@ -34,9 +46,22 @@ func NewApp() *App { | |||||
| 34 | PublicFS: webui.PublicFS, | 46 | PublicFS: webui.PublicFS, | ||
| 35 | BuildVersion: BuildVersion, | 47 | BuildVersion: BuildVersion, | ||
| 36 | BuildDir: BuildDir, | 48 | BuildDir: BuildDir, | ||
| 49 | + | DBDir: DBDir, | |||
| 50 | + | DBFilename: DBFilename, | |||
| 37 | TopDomain: TopDomain, | 51 | TopDomain: TopDomain, | ||
| 38 | SubDomain: SubDomain, | 52 | SubDomain: SubDomain, | ||
| 39 | AdminPath: AdminPath, | 53 | AdminPath: AdminPath, | ||
| 40 | HttpPort: HttpPort, | 54 | HttpPort: HttpPort, | ||
| 41 | } | 55 | } | ||
| 42 | } | 56 | } | ||
| 57 | + | ||||
| 58 | + | func NewDockerClient() *App { | |||
| 59 | + | return &App{ | |||
| 60 | + | DockerSock: DockerSock, | |||
| 61 | + | PodmanEnable: PodmanEnable, | |||
| 62 | + | PodmanSock: PodmanSock, | |||
| 63 | + | } | |||
| 64 | + | } | |||
| 65 | + | func (a *App) GetDBFilePath() string { | |||
| 66 | + | return filepath.Join(a.DBDir, a.DBFilename) | |||
| 67 | + | } | |||
A internal/app/sqlite.go +108 -0
| @@ -0,0 +1,108 @@ | |||
| 1 | + | package app | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "database/sql" | |
| 6 | + | "fmt" | |
| 7 | + | "os" | |
| 8 | + | ||
| 9 | + | sqlite3 "github.com/mattn/go-sqlite3" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // Initialize in-memory SQLite database and returns its connection | |
| 13 | + | func InitDB() (db *sql.DB, err error) { | |
| 14 | + | db, err = sql.Open("sqlite3", "file::memory?cache=shared") | |
| 15 | + | if err != nil { | |
| 16 | + | return nil, fmt.Errorf("failed to open in-memory database: %v", err) | |
| 17 | + | } | |
| 18 | + | return | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // CloseDB closes the database connection and backs up the in-memory database to disk | |
| 22 | + | func CloseDB(db *sql.DB, a *App) error { | |
| 23 | + | if err := BackupDB(db, a); err != nil { | |
| 24 | + | return err | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | if err := db.Close(); err != nil { | |
| 28 | + | return fmt.Errorf("failed to close database: %v", err) | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | return nil | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // LoadDBIntoMemory loads the database from disk into memory | |
| 35 | + | func LoadDBIntoMemory(a *App) (*sql.DB, error) { | |
| 36 | + | dbfp := a.GetDBFilePath() | |
| 37 | + | ||
| 38 | + | if _, err := os.Stat(dbfp); err != nil { | |
| 39 | + | return nil, nil | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | memDB, err := InitDB() | |
| 43 | + | if err != nil { | |
| 44 | + | return nil, err | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | diskDB, err := sql.Open("sqlite3", dbfp) | |
| 48 | + | if err != nil { | |
| 49 | + | return nil, fmt.Errorf("failed to open database file: %v", err) | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | if err := transferData(diskDB, memDB); err != nil { | |
| 53 | + | return nil, err | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | return memDB, nil | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | // transferData transfers data from srcDB to dstDB | |
| 60 | + | func transferData(srcDB, dstDB *sql.DB) error { | |
| 61 | + | srcConn, err := srcDB.Conn(context.Background()) | |
| 62 | + | if err != nil { | |
| 63 | + | return fmt.Errorf("failed to get connection from srcDB: %v", err) | |
| 64 | + | } | |
| 65 | + | defer srcConn.Close() | |
| 66 | + | ||
| 67 | + | dstConn, err := dstDB.Conn(context.Background()) | |
| 68 | + | if err != nil { | |
| 69 | + | return fmt.Errorf("failed to get connection from dstDB: %v", err) | |
| 70 | + | } | |
| 71 | + | defer dstConn.Close() | |
| 72 | + | ||
| 73 | + | return srcConn.Raw(func(srcRawConn interface{}) error { | |
| 74 | + | return dstConn.Raw(func(dstRawConn interface{}) error { | |
| 75 | + | srcSqlite3Conn := srcRawConn.(*sqlite3.SQLiteConn) | |
| 76 | + | dstSqlite3Conn := dstRawConn.(*sqlite3.SQLiteConn) | |
| 77 | + | ||
| 78 | + | _, err := srcSqlite3Conn.Backup("main", dstSqlite3Conn, "main") | |
| 79 | + | return err | |
| 80 | + | }) | |
| 81 | + | }) | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | // BackupDB backs up the in-memory database to disk | |
| 85 | + | func BackupDB(db *sql.DB, a *App) error { | |
| 86 | + | dbfp := a.GetDBFilePath() | |
| 87 | + | ||
| 88 | + | conn, err := db.Conn(context.Background()) | |
| 89 | + | if err != nil { | |
| 90 | + | return fmt.Errorf("failed to get underlying database connection: %v", err) | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | return conn.Raw(func(sqliteConn interface{}) error { | |
| 94 | + | sqlite3Conn := sqliteConn.(*sqlite3.SQLiteConn) | |
| 95 | + | backupDB, err := sqlite3Conn.Backup(dbfp, sqlite3Conn, "main") | |
| 96 | + | if err != nil { | |
| 97 | + | return fmt.Errorf("failed to create backup: %v", err) | |
| 98 | + | } | |
| 99 | + | defer backupDB.Close() | |
| 100 | + | ||
| 101 | + | _, err = backupDB.Step(-1) | |
| 102 | + | if err != nil { | |
| 103 | + | return fmt.Errorf("failed to backup in-memory database: %v", err) | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | return nil | |
| 107 | + | }) | |
| 108 | + | } | |
| @@ -0,0 +1,108 @@ | |||||
| 1 | + | package app | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "database/sql" | |||
| 6 | + | "fmt" | |||
| 7 | + | "os" | |||
| 8 | + | ||||
| 9 | + | sqlite3 "github.com/mattn/go-sqlite3" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | // Initialize in-memory SQLite database and returns its connection | |||
| 13 | + | func InitDB() (db *sql.DB, err error) { | |||
| 14 | + | db, err = sql.Open("sqlite3", "file::memory?cache=shared") | |||
| 15 | + | if err != nil { | |||
| 16 | + | return nil, fmt.Errorf("failed to open in-memory database: %v", err) | |||
| 17 | + | } | |||
| 18 | + | return | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | // CloseDB closes the database connection and backs up the in-memory database to disk | |||
| 22 | + | func CloseDB(db *sql.DB, a *App) error { | |||
| 23 | + | if err := BackupDB(db, a); err != nil { | |||
| 24 | + | return err | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | if err := db.Close(); err != nil { | |||
| 28 | + | return fmt.Errorf("failed to close database: %v", err) | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | return nil | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // LoadDBIntoMemory loads the database from disk into memory | |||
| 35 | + | func LoadDBIntoMemory(a *App) (*sql.DB, error) { | |||
| 36 | + | dbfp := a.GetDBFilePath() | |||
| 37 | + | ||||
| 38 | + | if _, err := os.Stat(dbfp); err != nil { | |||
| 39 | + | return nil, nil | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | memDB, err := InitDB() | |||
| 43 | + | if err != nil { | |||
| 44 | + | return nil, err | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | diskDB, err := sql.Open("sqlite3", dbfp) | |||
| 48 | + | if err != nil { | |||
| 49 | + | return nil, fmt.Errorf("failed to open database file: %v", err) | |||
| 50 | + | } | |||
| 51 | + | ||||
| 52 | + | if err := transferData(diskDB, memDB); err != nil { | |||
| 53 | + | return nil, err | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | return memDB, nil | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | // transferData transfers data from srcDB to dstDB | |||
| 60 | + | func transferData(srcDB, dstDB *sql.DB) error { | |||
| 61 | + | srcConn, err := srcDB.Conn(context.Background()) | |||
| 62 | + | if err != nil { | |||
| 63 | + | return fmt.Errorf("failed to get connection from srcDB: %v", err) | |||
| 64 | + | } | |||
| 65 | + | defer srcConn.Close() | |||
| 66 | + | ||||
| 67 | + | dstConn, err := dstDB.Conn(context.Background()) | |||
| 68 | + | if err != nil { | |||
| 69 | + | return fmt.Errorf("failed to get connection from dstDB: %v", err) | |||
| 70 | + | } | |||
| 71 | + | defer dstConn.Close() | |||
| 72 | + | ||||
| 73 | + | return srcConn.Raw(func(srcRawConn interface{}) error { | |||
| 74 | + | return dstConn.Raw(func(dstRawConn interface{}) error { | |||
| 75 | + | srcSqlite3Conn := srcRawConn.(*sqlite3.SQLiteConn) | |||
| 76 | + | dstSqlite3Conn := dstRawConn.(*sqlite3.SQLiteConn) | |||
| 77 | + | ||||
| 78 | + | _, err := srcSqlite3Conn.Backup("main", dstSqlite3Conn, "main") | |||
| 79 | + | return err | |||
| 80 | + | }) | |||
| 81 | + | }) | |||
| 82 | + | } | |||
| 83 | + | ||||
| 84 | + | // BackupDB backs up the in-memory database to disk | |||
| 85 | + | func BackupDB(db *sql.DB, a *App) error { | |||
| 86 | + | dbfp := a.GetDBFilePath() | |||
| 87 | + | ||||
| 88 | + | conn, err := db.Conn(context.Background()) | |||
| 89 | + | if err != nil { | |||
| 90 | + | return fmt.Errorf("failed to get underlying database connection: %v", err) | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | return conn.Raw(func(sqliteConn interface{}) error { | |||
| 94 | + | sqlite3Conn := sqliteConn.(*sqlite3.SQLiteConn) | |||
| 95 | + | backupDB, err := sqlite3Conn.Backup(dbfp, sqlite3Conn, "main") | |||
| 96 | + | if err != nil { | |||
| 97 | + | return fmt.Errorf("failed to create backup: %v", err) | |||
| 98 | + | } | |||
| 99 | + | defer backupDB.Close() | |||
| 100 | + | ||||
| 101 | + | _, err = backupDB.Step(-1) | |||
| 102 | + | if err != nil { | |||
| 103 | + | return fmt.Errorf("failed to backup in-memory database: %v", err) | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | return nil | |||
| 107 | + | }) | |||
| 108 | + | } | |||
M internal/gotemplate/render/html.go +7 -5
| @@ -3,10 +3,10 @@ package render | |||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| 5 | 5 | "fmt" | |
| 6 | + | "github.com/bnema/gordon/internal/app" | |
| 6 | 7 | "html/template" | |
| 7 | 8 | "io/fs" | |
| 8 | - | ||
| 9 | - | "github.com/bnema/gordon/internal/app" | |
| 9 | + | "path" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | 12 | type Renderer struct { | |
| @@ -34,14 +34,16 @@ func (r *Renderer) Render(data interface{}, a *app.App) (string, error) { | |||
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | 36 | // GetRenderer function returns a new Renderer instance | |
| 37 | - | func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) { | |
| 37 | + | func GetHTMLRenderer(pathStr string, filename string, fs fs.FS, a *app.App) (*Renderer, error) { | |
| 38 | + | // Join path and filename | |
| 39 | + | fullPath := path.Join(pathStr, filename) | |
| 38 | 40 | // Check if the file exists in the provided fs.FS using fs.Open | |
| 39 | - | file, err := fs.Open(filename) | |
| 41 | + | file, err := fs.Open(fullPath) | |
| 40 | 42 | if err != nil { | |
| 41 | 43 | return nil, fmt.Errorf("failed to open %s: %w", filename, err) | |
| 42 | 44 | } | |
| 43 | 45 | file.Close() // Close the file after checking | |
| 44 | - | tmpl, err := template.New(filename).ParseFS(fs, filename) | |
| 46 | + | tmpl, err := template.New(filename).ParseFS(fs, fullPath) | |
| 45 | 47 | if err != nil { | |
| 46 | 48 | return nil, fmt.Errorf("failed to parse %s: %w", filename, err) | |
| 47 | 49 | } | |
| @@ -3,10 +3,10 @@ package render | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "bytes" | 4 | "bytes" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "github.com/bnema/gordon/internal/app" | |||
| 6 | "html/template" | 7 | "html/template" | ||
| 7 | "io/fs" | 8 | "io/fs" | ||
| 8 | - | 9 | + | "path" | |
| 9 | - | "github.com/bnema/gordon/internal/app" | |||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | type Renderer struct { | 12 | type Renderer struct { | ||
| @@ -34,14 +34,16 @@ func (r *Renderer) Render(data interface{}, a *app.App) (string, error) { | |||||
| 34 | } | 34 | } | ||
| 35 | 35 | ||||
| 36 | // GetRenderer function returns a new Renderer instance | 36 | // GetRenderer function returns a new Renderer instance | ||
| 37 | - | func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) { | 37 | + | func GetHTMLRenderer(pathStr string, filename string, fs fs.FS, a *app.App) (*Renderer, error) { |
| 38 | + | // Join path and filename | |||
| 39 | + | fullPath := path.Join(pathStr, filename) | |||
| 38 | // Check if the file exists in the provided fs.FS using fs.Open | 40 | // Check if the file exists in the provided fs.FS using fs.Open | ||
| 39 | - | file, err := fs.Open(filename) | 41 | + | file, err := fs.Open(fullPath) |
| 40 | if err != nil { | 42 | if err != nil { | ||
| 41 | return nil, fmt.Errorf("failed to open %s: %w", filename, err) | 43 | return nil, fmt.Errorf("failed to open %s: %w", filename, err) | ||
| 42 | } | 44 | } | ||
| 43 | file.Close() // Close the file after checking | 45 | file.Close() // Close the file after checking | ||
| 44 | - | tmpl, err := template.New(filename).ParseFS(fs, filename) | 46 | + | tmpl, err := template.New(filename).ParseFS(fs, fullPath) |
| 45 | if err != nil { | 47 | if err != nil { | ||
| 46 | return nil, fmt.Errorf("failed to parse %s: %w", filename, err) | 48 | return nil, fmt.Errorf("failed to parse %s: %w", filename, err) | ||
| 47 | } | 49 | } | ||
M internal/httpserve/handler/adminroutes.go +8 -10
| @@ -13,24 +13,22 @@ import ( | |||
| 13 | 13 | // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml | |
| 14 | 14 | func AdminRoute(c echo.Context, a *app.App) error { | |
| 15 | 15 | lang := c.Get(middleware.LangKey).(string) | |
| 16 | - | fmt.Println(lang) | |
| 17 | - | data := webui.StringsYamlData{} | |
| 18 | - | err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &data) | |
| 16 | + | yamlData := webui.StringsYamlData{} | |
| 17 | + | err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData) | |
| 19 | 18 | if err != nil { | |
| 20 | - | return fmt.Errorf("failed to read strings data from YAML: %w", err) | |
| 19 | + | return err | |
| 21 | 20 | } | |
| 22 | 21 | ||
| 23 | 22 | // Navigate inside the fs.FS to get the template | |
| 24 | - | ||
| 25 | - | rendererData, err := render.GetHTMLRenderer("html/admin/index.gohtml", a.TemplateFS, a) | |
| 23 | + | path := "html/admin" | |
| 24 | + | rendererData, err := render.GetHTMLRenderer(path, "index.gohtml", a.TemplateFS, a) | |
| 26 | 25 | if err != nil { | |
| 27 | - | return fmt.Errorf("failed to get renderer: %w", err) | |
| 26 | + | return err | |
| 28 | 27 | } | |
| 29 | 28 | ||
| 30 | - | renderedHTML, err := rendererData.Render(data, a) | |
| 31 | - | fmt.Println(renderedHTML) | |
| 29 | + | renderedHTML, err := rendererData.Render(yamlData.CurrentLang, a) | |
| 32 | 30 | if err != nil { | |
| 33 | - | return fmt.Errorf("failed to render template: %w", err) | |
| 31 | + | return err | |
| 34 | 32 | } | |
| 35 | 33 | ||
| 36 | 34 | return c.HTML(200, renderedHTML) | |
| @@ -13,24 +13,22 @@ import ( | |||||
| 13 | // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml | 13 | // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml | ||
| 14 | func AdminRoute(c echo.Context, a *app.App) error { | 14 | func AdminRoute(c echo.Context, a *app.App) error { | ||
| 15 | lang := c.Get(middleware.LangKey).(string) | 15 | lang := c.Get(middleware.LangKey).(string) | ||
| 16 | - | fmt.Println(lang) | 16 | + | yamlData := webui.StringsYamlData{} |
| 17 | - | data := webui.StringsYamlData{} | 17 | + | err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData) |
| 18 | - | err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &data) | |||
| 19 | if err != nil { | 18 | if err != nil { | ||
| 20 | - | return fmt.Errorf("failed to read strings data from YAML: %w", err) | 19 | + | return err |
| 21 | } | 20 | } | ||
| 22 | 21 | ||||
| 23 | // Navigate inside the fs.FS to get the template | 22 | // Navigate inside the fs.FS to get the template | ||
| 24 | - | 23 | + | path := "html/admin" | |
| 25 | - | rendererData, err := render.GetHTMLRenderer("html/admin/index.gohtml", a.TemplateFS, a) | 24 | + | rendererData, err := render.GetHTMLRenderer(path, "index.gohtml", a.TemplateFS, a) |
| 26 | if err != nil { | 25 | if err != nil { | ||
| 27 | - | return fmt.Errorf("failed to get renderer: %w", err) | 26 | + | return err |
| 28 | } | 27 | } | ||
| 29 | 28 | ||||
| 30 | - | renderedHTML, err := rendererData.Render(data, a) | 29 | + | renderedHTML, err := rendererData.Render(yamlData.CurrentLang, a) |
| 31 | - | fmt.Println(renderedHTML) | |||
| 32 | if err != nil { | 30 | if err != nil { | ||
| 33 | - | return fmt.Errorf("failed to render template: %w", err) | 31 | + | return err |
| 34 | } | 32 | } | ||
| 35 | 33 | ||||
| 36 | return c.HTML(200, renderedHTML) | 34 | return c.HTML(200, renderedHTML) | ||
A pkg/utils/docker/initclient.go +35 -0
| @@ -0,0 +1,35 @@ | |||
| 1 | + | package docker | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "fmt" | |
| 6 | + | "log" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/gordon/internal/app" | |
| 9 | + | "github.com/docker/docker/api/types" | |
| 10 | + | "github.com/docker/docker/client" | |
| 11 | + | "github.com/docker/docker/pkg/archive" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | var dockerClient *client.Client | |
| 15 | + | ||
| 16 | + | func init() { | |
| 17 | + | // Get Docker configuration from App | |
| 18 | + | appInstance := app.NewDockerClient() | |
| 19 | + | dockerSocket := appInstance.DockerSock | |
| 20 | + | podmanEnable := appInstance.PodmanEnable | |
| 21 | + | podmanSocket := appInstance.PodmanSock | |
| 22 | + | ||
| 23 | + | // Based on configuration, decide whether to use Docker or Podman | |
| 24 | + | socketPath := dockerSocket | |
| 25 | + | if podmanEnable { | |
| 26 | + | socketPath = podmanSocket | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // Initialize Docker client | |
| 30 | + | var err error | |
| 31 | + | dockerClient, err = client.NewClientWithOpts(client.WithHost(fmt.Sprintf("unix://%s", socketPath)), client.WithAPIVersionNegotiation()) | |
| 32 | + | if err != nil { | |
| 33 | + | log.Fatalf("Error initializing Docker client: %v", err) | |
| 34 | + | } | |
| 35 | + | } | |
| @@ -0,0 +1,35 @@ | |||||
| 1 | + | package docker | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "fmt" | |||
| 6 | + | "log" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/gordon/internal/app" | |||
| 9 | + | "github.com/docker/docker/api/types" | |||
| 10 | + | "github.com/docker/docker/client" | |||
| 11 | + | "github.com/docker/docker/pkg/archive" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | var dockerClient *client.Client | |||
| 15 | + | ||||
| 16 | + | func init() { | |||
| 17 | + | // Get Docker configuration from App | |||
| 18 | + | appInstance := app.NewDockerClient() | |||
| 19 | + | dockerSocket := appInstance.DockerSock | |||
| 20 | + | podmanEnable := appInstance.PodmanEnable | |||
| 21 | + | podmanSocket := appInstance.PodmanSock | |||
| 22 | + | ||||
| 23 | + | // Based on configuration, decide whether to use Docker or Podman | |||
| 24 | + | socketPath := dockerSocket | |||
| 25 | + | if podmanEnable { | |||
| 26 | + | socketPath = podmanSocket | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // Initialize Docker client | |||
| 30 | + | var err error | |||
| 31 | + | dockerClient, err = client.NewClientWithOpts(client.WithHost(fmt.Sprintf("unix://%s", socketPath)), client.WithAPIVersionNegotiation()) | |||
| 32 | + | if err != nil { | |||
| 33 | + | log.Fatalf("Error initializing Docker client: %v", err) | |||
| 34 | + | } | |||
| 35 | + | } | |||