Initial commit.
commit
44c589610c
|
@ -0,0 +1,76 @@
|
||||||
|
###############################################################################
|
||||||
|
# GO
|
||||||
|
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# WINDOWS
|
||||||
|
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# JETBRAINS
|
||||||
|
|
||||||
|
## Directory-based project format
|
||||||
|
.idea/
|
||||||
|
# if you remove the above rule, at least ignore user-specific stuff:
|
||||||
|
# .idea/workspace.xml
|
||||||
|
# .idea/tasks.xml
|
||||||
|
# and these sensitive or high-churn files:
|
||||||
|
# .idea/dataSources.ids
|
||||||
|
# .idea/dataSources.xml
|
||||||
|
# .idea/sqlDataSources.xml
|
||||||
|
# .idea/dynamic.xml
|
||||||
|
|
||||||
|
## File-based project format
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
## Additional for IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# generated by mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# generated by JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# generated by Crashlytics plugin (for Android Studio and Intellij)
|
||||||
|
com_crashlytics_export_strings.xml
|
|
@ -0,0 +1,17 @@
|
||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
services:
|
||||||
|
goconvey:
|
||||||
|
build: docker/images/convey
|
||||||
|
working_dir: /go/src/git.icedream.tech/icedream/loggalicious/backend
|
||||||
|
volumes:
|
||||||
|
- .:/go/src/git.icedream.tech/icedream/loggalicious/backend:ro
|
||||||
|
depends_on:
|
||||||
|
- couchdb
|
||||||
|
ports:
|
||||||
|
- 8123:8080
|
||||||
|
|
||||||
|
couchdb:
|
||||||
|
build: docker/images/couchdb
|
||||||
|
ports:
|
||||||
|
- 5984:5984
|
|
@ -0,0 +1,9 @@
|
||||||
|
FROM golang:1.9-alpine3.7
|
||||||
|
|
||||||
|
RUN apk add --no-cache git alpine-sdk
|
||||||
|
RUN go get -v github.com/smartystreets/goconvey
|
||||||
|
|
||||||
|
COPY entrypoint.sh /usr/local/bin/docker-convey-entrypoint
|
||||||
|
RUN chmod +x /usr/local/bin/docker-convey-entrypoint
|
||||||
|
ENTRYPOINT ["docker-convey-entrypoint"]
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/sh -ex
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo "Working dir: $(pwd)"
|
||||||
|
|
||||||
|
go get -d -v ./...
|
||||||
|
|
||||||
|
exec goconvey -host 0.0.0.0 -launchBrowser false "$@"
|
|
@ -0,0 +1,7 @@
|
||||||
|
FROM couchdb:2
|
||||||
|
|
||||||
|
COPY entrypoint.sh /usr/local/bin/docker-couchdb-entrypoint
|
||||||
|
RUN chmod +x /usr/local/bin/*
|
||||||
|
|
||||||
|
ENTRYPOINT ["docker-couchdb-entrypoint"]
|
||||||
|
CMD ["/opt/couchdb/bin/couchdb"]
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/sh -e
|
||||||
|
|
||||||
|
echo "Waiting for CouchDB to do first initial startup..."
|
||||||
|
/docker-entrypoint.sh "$@" >/dev/null 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
|
||||||
|
while ! curl --fail -s -o /dev/null http://127.0.0.1:5984; do
|
||||||
|
sleep 1
|
||||||
|
printf "."
|
||||||
|
done
|
||||||
|
|
||||||
|
echo " ok"
|
||||||
|
|
||||||
|
createdb() {
|
||||||
|
curl --fail -s -o /dev/null -X GET "http://127.0.0.1:5984/$1" ||\
|
||||||
|
curl --fail -s -o /dev/null -X PUT "http://127.0.0.1:5984/$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Ensuring initial databases exist..."
|
||||||
|
|
||||||
|
createdb _global_changes
|
||||||
|
createdb _metadata
|
||||||
|
createdb _replicator
|
||||||
|
createdb _users
|
||||||
|
|
||||||
|
kill "$pid"
|
||||||
|
|
||||||
|
echo "Waiting for shutdown..."
|
||||||
|
|
||||||
|
while curl --fail -o /dev/null -s http://127.0.0.1:5984; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "===================================="
|
||||||
|
echo "== ACTUAL COUCHDB LOG STARTS HERE =="
|
||||||
|
echo "===================================="
|
||||||
|
|
||||||
|
exec tini -- /docker-entrypoint.sh "$@"
|
|
@ -0,0 +1,53 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
couchdb "github.com/fjl/go-couchdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dbName_Logs = "logs"
|
||||||
|
dbName_LogDeltas = "log_deltas"
|
||||||
|
dbName_Migration = "migration"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DatabaseServer struct {
|
||||||
|
*couchdb.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDatabaseServer(rawurl string, rt http.RoundTripper) (server *DatabaseServer, err error) {
|
||||||
|
couchClient, err := couchdb.NewClient(rawurl, rt)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server = &DatabaseServer{
|
||||||
|
Client: couchClient,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DatabaseServer) Initialize() (err error) {
|
||||||
|
if _, err = s.Client.CreateDB(dbName_Logs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = s.Client.CreateDB(dbName_LogDeltas); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = s.Client.CreateDB(dbName_Migration); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DatabaseServer) GetLogsDatabase() (db *LogsDatabase) {
|
||||||
|
return &LogsDatabase{DB: s.Client.DB(dbName_Logs)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DatabaseServer) GetLogDeltasDatabase() (db *LogDeltasDatabase) {
|
||||||
|
return &LogDeltasDatabase{DB: s.Client.DB(dbName_LogDeltas)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DatabaseServer) GetMigrationDatabase() (db *MigrationDatabase) {
|
||||||
|
return &MigrationDatabase{DB: s.Client.DB(dbName_Migration)}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fjl/go-couchdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogDeltasDatabase struct {
|
||||||
|
*couchdb.DB
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fjl/go-couchdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogsDatabase struct {
|
||||||
|
*couchdb.DB
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/fjl/go-couchdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VersionDocumentId = "version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MigrationVersionDocument struct {
|
||||||
|
Rev string `json:"_rev,omitempty"`
|
||||||
|
Version semver.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
type MigrationDatabase struct {
|
||||||
|
*couchdb.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetVersion returns the current version as stored in the migration database.
|
||||||
|
If no document was found, defaults to returning "0.0.0" as semver.Version
|
||||||
|
object.
|
||||||
|
*/
|
||||||
|
func (mdb *MigrationDatabase) GetVersion() (version semver.Version, rev string, err error) {
|
||||||
|
v := new(MigrationVersionDocument)
|
||||||
|
err = mdb.DB.Get(VersionDocumentId, v, nil)
|
||||||
|
if err != nil {
|
||||||
|
isActuallyAnError := true
|
||||||
|
if cerr, ok := err.(*couchdb.Error); ok {
|
||||||
|
// CouchDB error
|
||||||
|
if cerr.StatusCode == http.StatusNotFound {
|
||||||
|
isActuallyAnError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isActuallyAnError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
rev = v.Rev
|
||||||
|
version = v.Version
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdb *MigrationDatabase) PutVersion(version semver.Version, rev string) (newrev string, err error) {
|
||||||
|
newrev, err = mdb.DB.Put(VersionDocumentId, MigrationVersionDocument{
|
||||||
|
Version: version,
|
||||||
|
}, rev)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.icedream.tech/icedream/loggalicious/backend/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// This MUST be here! NEVER EVER REMOVE THIS!
|
||||||
|
RegisterMigration("0.0.1", func(s *database.DatabaseServer) (err error) {
|
||||||
|
_, err = s.Client.CreateDB("migration")
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
RegisterMigration("0.0.1", func(s *database.DatabaseServer) (err error) {
|
||||||
|
if _, err = s.Client.CreateDB("logs"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = s.Client.CreateDB("log_deltas"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package migrations_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.icedream.tech/icedream/loggalicious/backend/internal/database"
|
||||||
|
"git.icedream.tech/icedream/loggalicious/backend/internal/database/migrations"
|
||||||
|
"github.com/fjl/go-couchdb"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
const couchDBUrl = "http://couchdb:5984"
|
||||||
|
|
||||||
|
func Test_Migration(t *testing.T) {
|
||||||
|
couchDB, err := couchdb.NewClient(couchDBUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db := &database.DatabaseServer{Client: couchDB}
|
||||||
|
|
||||||
|
// Wipe database
|
||||||
|
dbNames, err := couchDB.AllDBs()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, dbName := range dbNames {
|
||||||
|
if strings.HasPrefix(dbName, "_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = couchDB.DeleteDB(dbName)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Convey("Migrations", t, func() {
|
||||||
|
|
||||||
|
Convey("At least one version is registered", func() {
|
||||||
|
versions := migrations.RegisteredVersions()
|
||||||
|
|
||||||
|
So(len(versions), ShouldBeGreaterThan, 0)
|
||||||
|
|
||||||
|
for _, version := range versions {
|
||||||
|
Convey(fmt.Sprintf("Version %s should have at least one migration", version), func() {
|
||||||
|
So(len(migrations.RegisteredMigrations(version)), ShouldBeGreaterThan, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Migrations from blank setup works", func() {
|
||||||
|
oldVersion, _, err := db.GetMigrationDatabase().GetVersion()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = migrations.Run(db)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
version, _, err := db.GetMigrationDatabase().GetVersion()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(version, ShouldNotEqual, oldVersion)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.icedream.tech/icedream/loggalicious/backend/internal/database"
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/fjl/go-couchdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MigrationFunc func(*database.DatabaseServer) error
|
||||||
|
|
||||||
|
var (
|
||||||
|
orderedRegisteredVersions = []semver.Version{}
|
||||||
|
registeredMigrations = map[string][]MigrationFunc{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisteredVersions() []semver.Version {
|
||||||
|
return orderedRegisteredVersions
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisteredMigrations(version semver.Version) []MigrationFunc {
|
||||||
|
return registeredMigrations[version.String()]
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterVersionIfNotRegistered(version semver.Version) {
|
||||||
|
versionStr := version.String()
|
||||||
|
if _, ok := registeredMigrations[versionStr]; !ok {
|
||||||
|
registeredMigrations[versionStr] = []MigrationFunc{}
|
||||||
|
injected := false
|
||||||
|
for index, currentVersion := range orderedRegisteredVersions {
|
||||||
|
if currentVersion.GT(version) /* first version to be newer than this version */ {
|
||||||
|
newOrderedRegisteredVersions := append(orderedRegisteredVersions[0:index], version)
|
||||||
|
newOrderedRegisteredVersions = append(newOrderedRegisteredVersions, orderedRegisteredVersions[index:]...)
|
||||||
|
orderedRegisteredVersions = newOrderedRegisteredVersions
|
||||||
|
injected = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !injected {
|
||||||
|
orderedRegisteredVersions = append(orderedRegisteredVersions, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterMigration(versionStr string, cb MigrationFunc) {
|
||||||
|
version := MustParseVersion(versionStr)
|
||||||
|
RegisterVersionIfNotRegistered(version)
|
||||||
|
|
||||||
|
registeredMigrations[versionStr] = append(registeredMigrations[versionStr], cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParseVersion(version string) semver.Version {
|
||||||
|
semverVersion, err := semver.Parse(version)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return semverVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(s *database.DatabaseServer) (err error) {
|
||||||
|
// Fetch current version
|
||||||
|
migrationDatabase := s.GetMigrationDatabase()
|
||||||
|
version, rev, err := migrationDatabase.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
isActuallyAnError := true
|
||||||
|
if cerr, ok := err.(*couchdb.Error); ok {
|
||||||
|
// CouchDB error
|
||||||
|
if cerr.StatusCode == http.StatusNotFound {
|
||||||
|
isActuallyAnError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isActuallyAnError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
version = semver.Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 0,
|
||||||
|
Patch: 0,
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, targetVersion := range orderedRegisteredVersions {
|
||||||
|
// log.Println("Check if migration is needed:", targetVersion)
|
||||||
|
if targetVersion.GT(version) {
|
||||||
|
// log.Println("Migrating database:", version, "->", targetVersion)
|
||||||
|
for _, cb := range registeredMigrations[targetVersion.String()] {
|
||||||
|
err = cb(s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
version = targetVersion
|
||||||
|
rev, err = migrationDatabase.PutVersion(version, rev)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue