Initial commit.

master
Icedream 2018-02-05 17:18:33 +01:00
commit 44c589610c
Signed by: icedream
GPG Key ID: 1573F6D8EFE4D0CF
15 changed files with 484 additions and 0 deletions

76
.gitignore vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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 "$@"

View File

@ -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"]

View File

@ -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 "$@"

View File

@ -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)}
}

View File

@ -0,0 +1,9 @@
package database
import (
"github.com/fjl/go-couchdb"
)
type LogDeltasDatabase struct {
*couchdb.DB
}

View File

@ -0,0 +1,9 @@
package database
import (
"github.com/fjl/go-couchdb"
)
type LogsDatabase struct {
*couchdb.DB
}

View File

@ -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
}

View File

@ -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
})
}

View File

@ -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)
})
})
}

View File

@ -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
}

View File

@ -0,0 +1,4 @@
package internal
type Server struct {
}

5
backend/main.go Normal file
View File

@ -0,0 +1,5 @@
package main
func main() {
}