Skip to content

Commit 3cef35c

Browse files
committed
cmd/relui: add Docker and Postgres configuration
This adds a Dockerfile for relui, as well as a file for running integration tests against the database. It also adds a configuration for a development database, as well as a test environment. For golang/go#47401 Change-Id: I668c99d028265c865baa7e15dfe627423815af94 Reviewed-on: https://go-review.googlesource.com/c/build/+/347289 Trust: Alexander Rakoczy <[email protected]> Run-TryBot: Alexander Rakoczy <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent dc54d45 commit 3cef35c

File tree

8 files changed

+368
-9
lines changed

8 files changed

+368
-9
lines changed

cmd/relui/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2021 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
FROM golang:1.17 AS build
6+
7+
COPY go.mod /app/go.mod
8+
COPY go.sum /app/go.sum
9+
10+
WORKDIR /app
11+
12+
RUN go mod download
13+
14+
COPY . /app
15+
16+
RUN go build golang.org/x/build/cmd/relui
17+
18+
FROM marketplace.gcr.io/google/debian10:latest
19+
20+
RUN apt-get update && apt-get install -y \
21+
--no-install-recommends \
22+
tini
23+
24+
ARG PORT=8080
25+
ENV PORT=${PORT}
26+
EXPOSE ${PORT}
27+
28+
COPY --from=build /app/relui /app/relui
29+
WORKDIR /app
30+
ENTRYPOINT ["/usr/bin/tini", "--", "./relui"]

cmd/relui/Dockerfile.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2021 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
FROM golang:1.17 AS build
6+
7+
COPY go.mod /app/go.mod
8+
COPY go.sum /app/go.sum
9+
10+
WORKDIR /app
11+
12+
RUN go mod download
13+
14+
COPY . /app
15+
16+
RUN go build golang.org/x/build/cmd/relui
17+
18+
ENTRYPOINT go test -v ./cmd/relui/...

cmd/relui/Makefile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright 2021 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
VERSION := $(shell ../coordinator/version.sh)
6+
7+
.PHONY: dev
8+
dev: postgres-dev docker
9+
docker run --rm --name=relui-dev -v postgres-run-dev:/var/run/postgresql -p 8080:8080 golang/relui:$(VERSION)
10+
11+
RUNNING := $(shell docker ps -q -f Name=postgres-dev)
12+
.PHONY: postgres-dev
13+
postgres-dev: network-dev
14+
docker stop postgres-dev || true
15+
docker run -d --rm --name=postgres-dev --network=relui-dev -v postgres-data-dev:/var/lib/postgresql/data -v postgres-run-dev:/var/run/postgresql -e POSTGRES_HOST_AUTH_METHOD=trust postgres:13
16+
17+
.PHONY: network-dev
18+
network-dev:
19+
docker network inspect relui-dev -f "{{.Name}}" || docker network create -d bridge relui-dev
20+
21+
.PHONY: test
22+
test: postgres-dev docker-test
23+
docker run --rm --name=relui-test -v postgres-run-dev:/var/run/postgresql -e RELUI_TEST_DATABASE="host=/var/run/postgresql user=postgres database=relui-test" golang/relui-test:$(VERSION)
24+
25+
.PHONY: docker
26+
docker:
27+
docker build -f Dockerfile -t golang/relui:$(VERSION) ../..
28+
29+
.PHONY: docker-test
30+
docker-test:
31+
docker build -f Dockerfile.test -t golang/relui-test:$(VERSION) ../..

cmd/relui/main.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@
99
package main
1010

1111
import (
12+
"context"
13+
"flag"
1214
"log"
1315
"net/http"
1416
"os"
17+
)
1518

16-
"golang.org/x/build/internal/datastore/fake"
19+
var (
20+
pgConnect = flag.String("pg-connect", "host=/var/run/postgresql user=postgres database=relui-dev", "Postgres connection string or URI")
1721
)
1822

1923
func main() {
20-
d := &dsStore{client: &fake.Client{}}
24+
ctx := context.Background()
25+
d := new(pgStore)
26+
if err := d.Connect(ctx, *pgConnect); err != nil {
27+
log.Fatalf("d.Connect() = %v", err)
28+
}
29+
defer d.Close()
2130
s := &server{
2231
store: d,
2332
}

cmd/relui/store.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ package main
99

1010
import (
1111
"context"
12+
"errors"
13+
"fmt"
1214
"log"
1315

1416
"cloud.google.com/go/datastore"
1517
"github.com/googleapis/google-cloud-go-testing/datastore/dsiface"
18+
"github.com/jackc/pgx/v4"
19+
"github.com/jackc/pgx/v4/pgxpool"
1620
reluipb "golang.org/x/build/cmd/relui/protos"
1721
)
1822

23+
var errDBNotExist = errors.New("database does not exist")
24+
1925
// store is a persistence interface for saving data.
2026
type store interface {
2127
AddWorkflow(workflow *reluipb.Workflow) error
@@ -24,6 +30,115 @@ type store interface {
2430
Workflows() []*reluipb.Workflow
2531
}
2632

33+
// pgStore is a store backed by a Postgres database.
34+
type pgStore struct {
35+
db *pgxpool.Pool
36+
}
37+
38+
// Connect connects to the database using the credentials supplied in
39+
// the provided connString.
40+
//
41+
// Any key/value or URI string compatible with libpq is valid. If the
42+
// database does not exist, one will be created using the credentials
43+
// provided.
44+
func (p *pgStore) Connect(ctx context.Context, connString string) error {
45+
cfg, err := pgx.ParseConfig(connString)
46+
if err != nil {
47+
return fmt.Errorf("pgx.ParseConfig() = %w", err)
48+
}
49+
if err := CreateDBIfNotExists(ctx, cfg); err != nil {
50+
return err
51+
}
52+
pool, err := pgxpool.Connect(ctx, connString)
53+
if err != nil {
54+
return err
55+
}
56+
p.db = pool
57+
return nil
58+
}
59+
60+
// Close closes the pgxpool.Pool.
61+
func (p *pgStore) Close() {
62+
p.db.Close()
63+
}
64+
65+
// ConnectMaintenanceDB connects to the maintenance database using the
66+
// credentials from cfg. If maintDB is an empty string, the database
67+
// with the name cfg.User will be used.
68+
func ConnectMaintenanceDB(ctx context.Context, cfg *pgx.ConnConfig, maintDB string) (*pgx.Conn, error) {
69+
cfg = cfg.Copy()
70+
cfg.Database = maintDB
71+
return pgx.ConnectConfig(ctx, cfg)
72+
}
73+
74+
// CreateDBIfNotExists checks whether the given dbName is an existing
75+
// database, and creates one if not.
76+
func CreateDBIfNotExists(ctx context.Context, cfg *pgx.ConnConfig) error {
77+
exists, err := checkIfDBExists(ctx, cfg)
78+
if err != nil || exists {
79+
return err
80+
}
81+
conn, err := ConnectMaintenanceDB(ctx, cfg, "")
82+
if err != nil {
83+
return fmt.Errorf("ConnectMaintenanceDB = %w", err)
84+
}
85+
createSQL := fmt.Sprintf("CREATE DATABASE %s", pgx.Identifier{cfg.Database}.Sanitize())
86+
if _, err := conn.Exec(ctx, createSQL); err != nil {
87+
return fmt.Errorf("conn.Exec(%q) = %w", createSQL, err)
88+
}
89+
return nil
90+
}
91+
92+
// DropDB drops the database specified in cfg. An error returned if
93+
// the database does not exist.
94+
func DropDB(ctx context.Context, cfg *pgx.ConnConfig) error {
95+
exists, err := checkIfDBExists(ctx, cfg)
96+
if err != nil {
97+
return fmt.Errorf("p.checkIfDBExists() = %w", err)
98+
}
99+
if !exists {
100+
return errDBNotExist
101+
}
102+
conn, err := ConnectMaintenanceDB(ctx, cfg, "")
103+
if err != nil {
104+
return fmt.Errorf("ConnectMaintenanceDB = %w", err)
105+
}
106+
dropSQL := fmt.Sprintf("DROP DATABASE %s", pgx.Identifier{cfg.Database}.Sanitize())
107+
if _, err := conn.Exec(ctx, dropSQL); err != nil {
108+
return fmt.Errorf("conn.Exec(%q) = %w", dropSQL, err)
109+
}
110+
return nil
111+
}
112+
113+
func checkIfDBExists(ctx context.Context, cfg *pgx.ConnConfig) (bool, error) {
114+
conn, err := ConnectMaintenanceDB(ctx, cfg, "")
115+
if err != nil {
116+
return false, fmt.Errorf("ConnectMaintenanceDB = %w", err)
117+
}
118+
row := conn.QueryRow(ctx, "SELECT 1 from pg_database WHERE datname=$1 LIMIT 1", cfg.Database)
119+
var exists int
120+
if err := row.Scan(&exists); err != nil && err != pgx.ErrNoRows {
121+
return false, fmt.Errorf("row.Scan() = %w", err)
122+
}
123+
return exists == 1, nil
124+
}
125+
126+
func (*pgStore) AddWorkflow(workflow *reluipb.Workflow) error {
127+
return nil
128+
}
129+
130+
func (*pgStore) BuildableTask(workflowId, id string) *reluipb.BuildableTask {
131+
return nil
132+
}
133+
134+
func (*pgStore) Workflow(id string) *reluipb.Workflow {
135+
return nil
136+
}
137+
138+
func (*pgStore) Workflows() []*reluipb.Workflow {
139+
return nil
140+
}
141+
27142
var _ store = (*dsStore)(nil)
28143

29144
// dsStore is a store backed by Google Cloud Datastore.

cmd/relui/store_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"errors"
10+
"os"
11+
"testing"
12+
13+
"github.com/jackc/pgx/v4"
14+
)
15+
16+
const dbEnvKey = "RELUI_TEST_DATABASE"
17+
18+
func TestCreateDBIfNotExists(t *testing.T) {
19+
dbEnv := os.Getenv(dbEnvKey)
20+
if dbEnv == "" {
21+
t.Skipf("%q is not set. Skipping.", dbEnvKey)
22+
}
23+
if testing.Short() {
24+
t.Skip("Skipping database tests in short mode.")
25+
}
26+
27+
ctx := context.Background()
28+
cfg, err := pgx.ParseConfig(dbEnv)
29+
if err != nil {
30+
t.Fatalf("pgx.ParseConfig(os.Getenv(%q)) = %v, wanted no error", dbEnvKey, err)
31+
}
32+
testCfg := cfg.Copy()
33+
testCfg.Database = "relui-test-nonexistent"
34+
if err := DropDB(ctx, testCfg); err != nil && !errors.Is(err, errDBNotExist) {
35+
t.Fatalf("p.DropDB() = %v, wanted %q or nil", err, errDBNotExist)
36+
}
37+
exists, err := checkIfDBExists(ctx, testCfg)
38+
if exists || err != nil {
39+
t.Fatalf("p.checkIfDBExists() = %t, %v, wanted %t, nil", exists, err, false)
40+
}
41+
if err := CreateDBIfNotExists(ctx, testCfg); err != nil {
42+
t.Errorf("p.CreateDBIfNotExists() = %v, wanted no error", err)
43+
}
44+
exists, err = checkIfDBExists(ctx, testCfg)
45+
if !exists || err != nil {
46+
t.Fatalf("p.checkIfDBExists() = %t, %v, wanted %t, nil", exists, err, true)
47+
}
48+
defer DropDB(ctx, testCfg)
49+
// Create again with the same name.
50+
if err := CreateDBIfNotExists(ctx, testCfg); err != nil {
51+
t.Errorf("p.CreateDBIfNotExists() = %v, wanted no error", err)
52+
}
53+
exists, err = checkIfDBExists(ctx, testCfg)
54+
if !exists || err != nil {
55+
t.Fatalf("p.checkIfDBExists() = %t, %v, wanted %t, nil", exists, err, true)
56+
}
57+
}

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/NYTimes/gziphandler v1.1.1
1313
github.com/aws/aws-sdk-go v1.30.15
1414
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625
15-
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d
15+
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
1616
github.com/davecgh/go-spew v1.1.1
1717
github.com/gliderlabs/ssh v0.3.3
1818
github.com/golang/protobuf v1.4.3
@@ -23,8 +23,9 @@ require (
2323
github.com/googleapis/gax-go/v2 v2.0.5
2424
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8
2525
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
26+
github.com/jackc/pgx/v4 v4.13.0
2627
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1
27-
github.com/kr/pty v1.1.3
28+
github.com/kr/pty v1.1.8
2829
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
2930
go.opencensus.io v0.23.0
3031
go4.org v0.0.0-20180809161055-417644f6feb5

0 commit comments

Comments
 (0)