Skip to content

Commit

Permalink
Apply migrations when tink-server boots (#296)
Browse files Browse the repository at this point in the history
## Description

It will be nice to have the tink-server capable of applying migrations
if necessary. The idea to self-apply migrations will may look unusual
but it is an easy way to distribute software capable of migrating
database schema without having to run any external procedures.
## Why is this needed

Fixes: #295 

## How Has This Been Tested?

```console
$ docker run -d -it -e  POSTGRES_PASSWORD=tinkerbell -e POSTGRES_USER=tinkerbell -p 5432:5432 postgres
# PGPASSWORD=tinkerbell PGUSER=tinkerbell PGSSLMODE=disable PACKET_VERSION=v1 PACKET_ENV=ga ROLLBAR_TOKEN=ignored go run cmd/tink-server/main.go
{"level":"info","ts":1600772721.3009,"caller":"tink-server/main.go:30","msg":"starting version devel","service":"github.com/tinkerbell/tink"}
migrations  1
records  0
{"level":"info","ts":1600772721.309925,"caller":"tink-server/main.go:54","msg":"Your database schema is not up to date. Please apply migrations running tink-server with env var ONLY_MIGRATION set.","service":"github.com/tinkerbell/tink"}
{"level":"info","ts":1600772721.310011,"caller":"metrics/metrics.go:58","msg":"initializing label values","service":"github.com/tinkerbell/tink"}
```

As suggested you should run tink-server with ONLY_MIGRATION=true

```
$ ONLY_MIGRATION=true PGPASSWORD=tinkerbell PGUSER=tinkerbell PGSSLMODE=disable PACKET_VERSION=v1 PACKET_ENV=ga ROLLBAR_TOKEN=ignored go run cmd/tink-server/main.go
{"level":"info","ts":1600772853.779405,"caller":"tink-server/main.go:30","msg":"starting version devel","service":"github.com/tinkerbell/tink"}
{"level":"info","ts":1600772853.779805,"caller":"tink-server/main.go:40","msg":"Applying migrations. This process will end when migrations will take place.","service":"github.com/tinkerbell/tink"}
{"level":"info","ts":1600772853.819854,"caller":"tink-server/main.go:45","msg":"Running migrations if necessary","service":"github.com/tinkerbell/tink","num_applied_migrations":1}
```

## How are existing users impacted? What migration steps/scripts do we need?

They are not impacted by this change.

## Checklist:

I have:

- [ ] updated the documentation and/or roadmap (if required)
- [ ] added unit or e2e tests
- [ ] provided instructions on how to upgrade
  • Loading branch information
mergify[bot] authored Sep 28, 2020
2 parents 2b014b9 + 1381963 commit 11bd870
Show file tree
Hide file tree
Showing 8 changed files with 432 additions and 41 deletions.
31 changes: 27 additions & 4 deletions cmd/tink-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"syscall"

"github.com/packethost/pkg/log"
"github.com/tinkerbell/tink/db"
rpcServer "github.com/tinkerbell/tink/grpc-server"
httpServer "github.com/tinkerbell/tink/http-server"
)
Expand All @@ -20,7 +21,6 @@ var (

func main() {
log, err := log.Init("github.com/tinkerbell/tink")

if err != nil {
panic(err)
}
Expand All @@ -32,7 +32,30 @@ func main() {
errCh := make(chan error, 2)
facility := os.Getenv("FACILITY")

cert, modT := rpcServer.SetupGRPC(ctx, logger, facility, errCh)
tinkDB := db.Connect(logger)

_, onlyMigration := os.LookupEnv("ONLY_MIGRATION")
if onlyMigration {
logger.Info("Applying migrations. This process will end when migrations will take place.")
numAppliedMigrations, err := tinkDB.Migrate()
if err != nil {
log.Fatal(err)
panic(err)
}
log.With("num_applied_migrations", numAppliedMigrations).Info("Migrations applied successfully")
os.Exit(0)
}

numAvailableMigrations, err := tinkDB.CheckRequiredMigrations()
if err != nil {
log.Fatal(err)
panic(err)
}
if numAvailableMigrations != 0 {
log.Info("Your database schema is not up to date. Please apply migrations running tink-server with env var ONLY_MIGRATION set.")
}

cert, modT := rpcServer.SetupGRPC(ctx, logger, facility, tinkDB, errCh)
httpServer.SetupHTTP(ctx, logger, cert, modT, errCh)

sigs := make(chan os.Signal, 1)
Expand All @@ -49,12 +72,12 @@ func main() {
// wait for grpc server to shutdown
err = <-errCh
if err != nil {
logger.Error(err)
log.Fatal(err)
panic(err)
}
err = <-errCh
if err != nil {
logger.Error(err)
log.Fatal(err)
panic(err)
}
}
15 changes: 15 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/lib/pq"
"github.com/packethost/pkg/log"
"github.com/pkg/errors"
migrate "github.com/rubenv/sql-migrate"
"github.com/tinkerbell/tink/db/migration"
pb "github.com/tinkerbell/tink/protos/workflow"
)

Expand Down Expand Up @@ -73,6 +75,19 @@ func Connect(lg log.Logger) *TinkDB {
return &TinkDB{instance: db}
}

func (t *TinkDB) Migrate() (int, error) {
return migrate.Exec(t.instance, "postgres", migration.GetMigrations(), migrate.Up)
}

func (t *TinkDB) CheckRequiredMigrations() (int, error) {
migrations := migration.GetMigrations().Migrations
records, err := migrate.GetMigrationRecords(t.instance, "postgres")
if err != nil {
return 0, err
}
return len(migrations) - len(records), nil
}

// Error returns the underlying cause for error
func Error(err error) *pq.Error {
if pqErr, ok := errors.Cause(err).(*pq.Error); ok {
Expand Down
87 changes: 87 additions & 0 deletions db/migration/202009171251-init-database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package migration

import migrate "github.com/rubenv/sql-migrate"

func Get202009171251() *migrate.Migration {
return &migrate.Migration{
Id: "202009171251-init-database",
Up: []string{`
SET ROLE tinkerbell;
SELECT 'CREATE DATABASE tinkerbell' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'tinkerbell');
CREATE TABLE IF NOT EXISTS hardware (
id UUID UNIQUE
, inserted_at TIMESTAMPTZ
, deleted_at TIMESTAMPTZ
, data JSONB
);
CREATE INDEX IF NOT EXISTS idx_id ON hardware (id);
CREATE INDEX IF NOT EXISTS idx_deleted_at ON hardware (deleted_at NULLS FIRST);
CREATE INDEX IF NOT EXISTS idxgin_type ON hardware USING GIN (data JSONB_PATH_OPS);
CREATE TABLE IF NOT EXISTS template (
id UUID UNIQUE NOT NULL
, name VARCHAR(200) UNIQUE NOT NULL
, created_at TIMESTAMPTZ
, updated_at TIMESTAMPTZ
, deleted_at TIMESTAMPTZ
, data BYTEA
CONSTRAINT CK_name CHECK (name ~ '^[a-zA-Z0-9_-]*$')
);
CREATE INDEX IF NOT EXISTS idx_tid ON template (id);
CREATE INDEX IF NOT EXISTS idx_tdeleted_at ON template (deleted_at NULLS FIRST);
CREATE TABLE IF NOT EXISTS workflow (
id UUID UNIQUE NOT NULL
, template UUID NOT NULL
, devices JSONB NOT NULL
, created_at TIMESTAMPTZ
, updated_at TIMESTAMPTZ
, deleted_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_wid ON workflow (id);
CREATE INDEX IF NOT EXISTS idx_wdeleted_at ON workflow (deleted_at NULLS FIRST);
CREATE TABLE IF NOT EXISTS workflow_state (
workflow_id UUID UNIQUE NOT NULL
, current_task_name VARCHAR(200)
, current_action_name VARCHAR(200)
, current_action_state SMALLINT
, current_worker VARCHAR(200)
, action_list JSONB
, current_action_index int
, total_number_of_actions INT
);
CREATE INDEX IF NOT EXISTS idx_wfid ON workflow_state (workflow_id);
CREATE TABLE IF NOT EXISTS workflow_event (
workflow_id UUID NOT NULL
, worker_id UUID NOT NULL
, task_name VARCHAR(200)
, action_name VARCHAR(200)
, execution_time int
, message VARCHAR(200)
, status SMALLINT
, created_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_event ON workflow_event (created_at);
CREATE TABLE IF NOT EXISTS workflow_worker_map (
workflow_id UUID NOT NULL
, worker_id UUID NOT NULL
);
CREATE TABLE IF NOT EXISTS workflow_data (
workflow_id UUID NOT NULL
, version INT
, metadata JSONB
, data JSONB
);`},
}
}
11 changes: 11 additions & 0 deletions db/migration/migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package migration

import migrate "github.com/rubenv/sql-migrate"

func GetMigrations() *migrate.MemoryMigrationSource {
return &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
Get202009171251(),
},
}
}
2 changes: 0 additions & 2 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,9 @@ services:
image: postgres:10-alpine
restart: unless-stopped
environment:
POSTGRES_DB: tinkerbell
POSTGRES_PASSWORD: tinkerbell
POSTGRES_USER: tinkerbell
volumes:
- ./db/tinkerbell-init.sql:/docker-entrypoint-initdb.d/tinkerbell-init.sql:ro
- postgres_data:/var/lib/postgresql/data:rw
ports:
- 5432:5432
Expand Down
10 changes: 4 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ require (
github.com/go-openapi/strfmt v0.19.3 // indirect
github.com/golang/protobuf v1.4.2
github.com/google/uuid v1.1.2
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/grpc-gateway v1.14.6
github.com/jedib0t/go-pretty v4.3.0+incompatible
Expand All @@ -26,18 +24,18 @@ require (
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/packethost/pkg v0.0.0-20200903155310-0433e0605550
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.1.0
github.com/prometheus/client_golang v1.3.0
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.3.0
github.com/stretchr/testify v1.4.0
go.mongodb.org/mongo-driver v1.1.2 // indirect
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
google.golang.org/grpc v1.29.1
google.golang.org/protobuf v1.25.0
gopkg.in/yaml.v2 v2.2.3
gopkg.in/yaml.v2 v2.2.5
gotest.tools v2.2.0+incompatible // indirect
)
Loading

0 comments on commit 11bd870

Please sign in to comment.