Skip to content

Commit fed0c76

Browse files
committed
config: add multiple database configuration
This will give us the ability to add additional database backends if the need arises. Signed-off-by: Hank Donnay <[email protected]>
1 parent eb54b88 commit fed0c76

File tree

6 files changed

+184
-29
lines changed

6 files changed

+184
-29
lines changed

config/database.go

+57-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package config
22

33
import (
4+
"fmt"
45
"net/url"
56
"os"
67
"strings"
78
)
89

9-
func checkDSN(s string) (w []Warning, err error) {
10+
var errConnString = Warning{
11+
path: ".connstring",
12+
inner: fmt.Errorf(`using bare-string for database configuration deprecated: %w`, ErrDeprecated),
13+
}
14+
15+
func checkPostgresqlDSN(s string) (w []Warning) {
1016
switch {
1117
case s == "":
1218
// Nothing specified, make sure something's in the environment.
@@ -38,5 +44,54 @@ func checkDSN(s string) (w []Warning, err error) {
3844
msg: "unable to make sense of connection string",
3945
})
4046
}
41-
return w, nil
47+
return w
48+
}
49+
50+
// Database indicates the database configuration.
51+
type Database struct {
52+
// Name indicates which database backend to use.
53+
//
54+
// This value must match the json/yaml tag.
55+
Name string `json:"name" yaml:"name"`
56+
// Migrations indicates if database migrations should run automatically.
57+
Migrations *bool `json:"migrations,omitempty" yaml:"migrations,omitempty"`
58+
// PostgreSQL is the PostgreSQL configuration.
59+
PostgreSQL *DatabasePostgreSQL `json:"postgresql,omitempty" yaml:"postgresql,omitempty"`
60+
}
61+
62+
func (d *Database) lint() (ws []Warning, err error) {
63+
switch n := d.Name; n {
64+
case "postgresql": // OK
65+
case "postgres":
66+
ws = append(ws, Warning{
67+
msg: fmt.Sprintf("unknown database: %q (did you mean %q?)", n, "postgresql"),
68+
path: ".name",
69+
})
70+
default:
71+
ws = append(ws, Warning{
72+
msg: fmt.Sprintf("unknown database: %q", n),
73+
path: ".name",
74+
})
75+
}
76+
return ws, nil
77+
}
78+
func (d *Database) validate(_ Mode) ([]Warning, error) {
79+
return d.lint()
80+
}
81+
82+
// DatabasePostgreSQL is the PostgreSQL-specific database configuration.
83+
type DatabasePostgreSQL struct {
84+
// DSN is a data source name (aka "connection string") as documented for
85+
// [libpq], with the extensions supported by [pgxpool].
86+
//
87+
// [libpq]: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
88+
// [pgxpool]: https://pkg.go.dev/github.com/jackc/pgx/v4/pgxpool#ParseConfig
89+
DSN string `json:"dsn" yaml:"dsn"`
90+
}
91+
92+
func (d *DatabasePostgreSQL) lint() ([]Warning, error) {
93+
return checkPostgresqlDSN(d.DSN), nil
94+
}
95+
func (d *DatabasePostgreSQL) validate(_ Mode) ([]Warning, error) {
96+
return d.lint()
4297
}

config/database_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
)
9+
10+
func TestDatabaseUnmarshal(t *testing.T) {
11+
want := Database{
12+
Name: "postgresql",
13+
PostgreSQL: &DatabasePostgreSQL{
14+
DSN: "host=test",
15+
},
16+
}
17+
input := []string{
18+
`{"name":"postgresql","postgresql":{"dsn":"host=test"}}`,
19+
}
20+
21+
for _, tc := range input {
22+
t.Logf("testing: %#q", tc)
23+
var got Database
24+
if err := json.Unmarshal([]byte(tc), &got); err != nil {
25+
t.Error(err)
26+
continue
27+
}
28+
ws, err := got.lint()
29+
if err != nil {
30+
t.Error(err)
31+
continue
32+
}
33+
for _, w := range ws {
34+
t.Logf("got lint: %v", &w)
35+
}
36+
if !cmp.Equal(&got, &want) {
37+
t.Error(cmp.Diff(&got, &want))
38+
}
39+
}
40+
}

config/indexer.go

+26-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ type Indexer struct {
1212
// url: "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"
1313
// or
1414
// string: "user=pqgotest dbname=pqgotest sslmode=verify-full"
15-
ConnString string `yaml:"connstring" json:"connstring"`
15+
//
16+
// Deprecated: Use the ".database" member instead.
17+
ConnString string `yaml:"connstring,omitempty" json:"connstring,omitempty"`
18+
// Database is the database configuration.
19+
Database *Database `yaml:"database,omitempty" json:"database,omitempty"`
1620
// A positive value representing seconds.
1721
//
1822
// Concurrent Indexers lock on manifest scans to avoid clobbering.
@@ -34,7 +38,9 @@ type Indexer struct {
3438
// A "true" or "false" value
3539
//
3640
// Whether Indexer nodes handle migrations to their database.
37-
Migrations bool `yaml:"migrations,omitempty" json:"migrations,omitempty"`
41+
//
42+
// Deprecated: Use the ".database.migrations" member instead.
43+
Migrations *bool `yaml:"migrations,omitempty" json:"migrations,omitempty"`
3844
// Airgap disables HTTP access to the Internet. This affects both indexers and
3945
// the layer fetcher. Database connections are unaffected.
4046
//
@@ -66,17 +72,30 @@ func (i *Indexer) validate(mode Mode) (ws []Warning, err error) {
6672
msg: `automatically sizing number of concurrent requests`,
6773
})
6874
}
75+
if i.ConnString != "" {
76+
ws = append(ws, errConnString)
77+
i.ConnString = ""
78+
if d := i.Database; d != nil {
79+
d.Name = `postgresql`
80+
d.PostgreSQL = &DatabasePostgreSQL{
81+
DSN: i.ConnString,
82+
}
83+
d.Migrations = i.Migrations
84+
}
85+
}
6986
lws, err := i.lint()
7087
return append(ws, lws...), err
7188
}
7289

7390
func (i *Indexer) lint() (ws []Warning, err error) {
74-
ws, err = checkDSN(i.ConnString)
75-
if err != nil {
76-
return ws, err
91+
if i.ConnString != "" {
92+
ws = append(ws, errConnString)
7793
}
78-
for i := range ws {
79-
ws[i].path = ".connstring"
94+
if i.Database == nil {
95+
ws = append(ws, Warning{
96+
path: ".database",
97+
msg: `missing database configuration`,
98+
})
8099
}
81100
if i.ScanLockRetry > 10 { // Guess at what a "large" value is here.
82101
ws = append(ws, Warning{

config/lint_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ func ExampleLint() {
1414
// error: <nil>
1515
// warning: http listen address not provided, default will be used (at $.http_listen_addr)
1616
// warning: introspection address not provided, default will be used (at $.introspection_addr)
17-
// warning: connection string is empty and no relevant environment variables found (at $.indexer.connstring)
18-
// warning: connection string is empty and no relevant environment variables found (at $.matcher.connstring)
17+
// warning: missing database configuration (at $.indexer.database)
18+
// warning: missing database configuration (at $.matcher.database)
1919
// warning: updater period is very aggressive: most sources are updated daily (at $.matcher.period)
2020
// warning: update garbage collection is off (at $.matcher.update_retention)
21-
// warning: connection string is empty and no relevant environment variables found (at $.notifier.connstring)
21+
// warning: missing database configuration (at $.notifier.database)
2222
// warning: interval is very fast: may result in increased workload (at $.notifier.poll_interval)
2323
// warning: interval is very fast: may result in increased workload (at $.notifier.delivery_interval)
2424
}

config/matcher.go

+29-9
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ type Matcher struct {
1313
// url: "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"
1414
// or
1515
// string: "user=pqgotest dbname=pqgotest sslmode=verify-full"
16+
//
17+
// Deprecated: Use the ".database" member instead.
1618
ConnString string `yaml:"connstring" json:"connstring"`
19+
// Database is the database configuration.
20+
Database *Database `yaml:"database,omitempty" json:"database,omitempty"`
1721
// A string in <host>:<port> format where <host> can be an empty string.
1822
//
1923
// A Matcher contacts an Indexer to create a VulnerabilityReport.
@@ -36,7 +40,7 @@ type Matcher struct {
3640
// Clair allows for a custom connection pool size. This number will
3741
// directly set how many active sql connections are allowed concurrently.
3842
//
39-
// Deprecated: Pool size should be set through the ConnString member.
43+
// Deprecated: Pool size should be set through the database configuration.
4044
// Currently, Clair only uses the "pgxpool" package to connect to the
4145
// database, so see
4246
// https://pkg.go.dev/github.com/jackc/pgx/v4/pgxpool#ParseConfig for more
@@ -51,15 +55,17 @@ type Matcher struct {
5155
// A "true" or "false" value
5256
//
5357
// Whether Matcher nodes handle migrations to their databases.
54-
Migrations bool `yaml:"migrations,omitempty" json:"migrations,omitempty"`
58+
//
59+
// Deprecated: Use the ".database.migrations" member instead.
60+
Migrations *bool `yaml:"migrations,omitempty" json:"migrations,omitempty"`
5561
// DisableUpdaters disables the updater's running of matchers.
5662
//
5763
// This should be toggled on if vulnerabilities are being provided by
5864
// another mechanism.
5965
DisableUpdaters bool `yaml:"disable_updaters,omitempty" json:"disable_updaters,omitempty"`
6066
}
6167

62-
func (m *Matcher) validate(mode Mode) ([]Warning, error) {
68+
func (m *Matcher) validate(mode Mode) (ws []Warning, err error) {
6369
if mode != ComboMode && mode != MatcherMode {
6470
return nil, nil
6571
}
@@ -90,16 +96,30 @@ func (m *Matcher) validate(mode Mode) ([]Warning, error) {
9096
default:
9197
panic("programmer error")
9298
}
93-
return m.lint()
99+
if m.ConnString != "" {
100+
ws = append(ws, errConnString)
101+
m.ConnString = ""
102+
if d := m.Database; d != nil {
103+
d.Name = `postgresql`
104+
d.PostgreSQL = &DatabasePostgreSQL{
105+
DSN: m.ConnString,
106+
}
107+
d.Migrations = m.Migrations
108+
}
109+
}
110+
lws, err := m.lint()
111+
return append(ws, lws...), err
94112
}
95113

96114
func (m *Matcher) lint() (ws []Warning, err error) {
97-
ws, err = checkDSN(m.ConnString)
98-
if err != nil {
99-
return ws, err
115+
if m.ConnString != "" {
116+
ws = append(ws, errConnString)
100117
}
101-
for i := range ws {
102-
ws[i].path = ".connstring"
118+
if m.Database == nil {
119+
ws = append(ws, Warning{
120+
path: ".database",
121+
msg: `missing database configuration`,
122+
})
103123
}
104124

105125
if m.Period < Duration(DefaultMatcherPeriod) {

config/notifier.go

+29-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ type Notifier struct {
2626
// url: "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"
2727
// or
2828
// string: "user=pqgotest dbname=pqgotest sslmode=verify-full"
29+
//
30+
// Deprecated: Use the ".database" member instead.
2931
ConnString string `yaml:"connstring" json:"connstring"`
32+
// Database is the database configuration.
33+
Database *Database `yaml:"database,omitempty" json:"database,omitempty"`
3034
// A string in <host>:<port> format where <host> can be an empty string.
3135
//
3236
// A Notifier contacts an Indexer to create obtain manifests affected by vulnerabilities.
@@ -63,10 +67,12 @@ type Notifier struct {
6367
// A "true" or "false" value
6468
//
6569
// Whether Notifier nodes handle migrations to their database.
66-
Migrations bool `yaml:"migrations,omitempty" json:"migrations,omitempty"`
70+
//
71+
// Deprecated: Use the ".database.migrations" member instead.
72+
Migrations *bool `yaml:"migrations,omitempty" json:"migrations,omitempty"`
6773
}
6874

69-
func (n *Notifier) validate(mode Mode) ([]Warning, error) {
75+
func (n *Notifier) validate(mode Mode) (ws []Warning, err error) {
7076
if mode != ComboMode && mode != NotifierMode {
7177
return nil, nil
7278
}
@@ -88,17 +94,32 @@ func (n *Notifier) validate(mode Mode) ([]Warning, error) {
8894
default:
8995
panic("programmer error")
9096
}
91-
return n.lint()
97+
if n.ConnString != "" {
98+
ws = append(ws, errConnString)
99+
n.ConnString = ""
100+
if d := n.Database; d != nil {
101+
d.Name = `postgresql`
102+
d.PostgreSQL = &DatabasePostgreSQL{
103+
DSN: n.ConnString,
104+
}
105+
d.Migrations = n.Migrations
106+
}
107+
}
108+
lws, err := n.lint()
109+
return append(ws, lws...), err
92110
}
93111

94112
func (n *Notifier) lint() (ws []Warning, err error) {
95-
ws, err = checkDSN(n.ConnString)
96-
if err != nil {
97-
return ws, err
113+
if n.ConnString != "" {
114+
ws = append(ws, errConnString)
98115
}
99-
for i := range ws {
100-
ws[i].path = ".connstring"
116+
if n.Database == nil {
117+
ws = append(ws, Warning{
118+
path: ".database",
119+
msg: `missing database configuration`,
120+
})
101121
}
122+
102123
got := 0
103124
if n.AMQP != nil {
104125
got++

0 commit comments

Comments
 (0)