Skip to content
This repository was archived by the owner on Apr 2, 2024. It is now read-only.

Commit d9e4f17

Browse files
committed
Allow timescale_prometheus_extra installation without superuser
We expect most of our users to run the connector without superuser, as is best practice. This commit enables the installation of the timescale_prometheus_extra extension without using superuser, via the pgextwhlist extension. To enable this, we add an additional catalog table to the public schema, including information about the connector's installation, most importantly the schemas it's storing things in. We then use this in the extension to setup the search_path for extension creation. We need to do this since we do not want to hardcode the schemas we use, but we cannot easily reset the search path to the one set by the connector, due to the way pgextwlist runs the extension-creation scripts. This commit also makes our test code run as non-SUPERUSER wherever possible.
1 parent 9a737ae commit d9e4f17

File tree

14 files changed

+197
-89
lines changed

14 files changed

+197
-89
lines changed

extension/003-enable-pgextwlist.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
set -ex
3+
4+
sed -i -e "s/#shared_preload_libraries/shared_preload_libraries/" \
5+
/var/lib/postgresql/data/postgresql.conf \
6+
7+
sed -i \
8+
-e "s/shared_preload_libraries = '/shared_preload_libraries = 'pgextwlist,/" \
9+
/var/lib/postgresql/data/postgresql.conf
10+
11+
echo "extwlist.extensions = 'timescale_prometheus_extra,timescaledb'" >> \
12+
/var/lib/postgresql/data/postgresql.conf

extension/Dockerfile

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ FROM timescale/timescaledb:${TIMESCALEDB_VERSION}-${PG_VERSION_TAG}
44

55
MAINTAINER Timescale https://www.timescale.com
66

7-
COPY timescale_prometheus_extra.control Makefile /build/timescale-prometheus/
8-
COPY src/*.c src/*.h /build/timescale-prometheus/src/
9-
COPY sql/timescale-prometheus.sql /build/timescale-prometheus/sql/
10-
117
RUN set -ex \
128
&& apk add --no-cache --virtual .build-deps \
139
coreutils \
@@ -16,10 +12,26 @@ RUN set -ex \
1612
libc-dev \
1713
make \
1814
util-linux-dev \
19-
clang \
20-
llvm \
21-
\
15+
clang \
16+
llvm \
17+
git
18+
RUN set -ex \
19+
&& git clone --branch v1.9 --depth 1 \
20+
https://github.com/dimitri/pgextwlist.git /pgextwlist \
21+
&& cd /pgextwlist \
22+
&& make \
23+
&& make install \
24+
&& cp /pgextwlist/pgextwlist.so `pg_config --pkglibdir`/plugins \
25+
&& rm -rf /pgextwlist
26+
27+
COPY timescale_prometheus_extra.control Makefile /build/timescale-prometheus/
28+
COPY src/*.c src/*.h /build/timescale-prometheus/src/
29+
COPY sql/timescale-prometheus.sql /build/timescale-prometheus/sql/
30+
31+
RUN set -ex \
2232
&& make -C /build/timescale-prometheus install \
2333
\
2434
&& apk del .build-deps \
2535
&& rm -rf /build
36+
37+
COPY 003-enable-pgextwlist.sh /docker-entrypoint-initdb.d/

extension/sql/timescale-prometheus.sql

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,53 @@
11

22
DO $$
33
DECLARE
4-
version INT;
5-
dirty BOOLEAN;
4+
current_version INT;
5+
is_dirty BOOLEAN;
6+
original_message TEXT;
67
BEGIN
78
BEGIN
89
SELECT version, dirty
9-
INTO STRICT version, dirty
10-
FROM public.prom_schema_migrations;
10+
INTO STRICT current_version, is_dirty
11+
FROM public.prom_schema_migrations;
1112
EXCEPTION WHEN OTHERS THEN
12-
RAISE EXCEPTION 'could not determine the version of the timescale-prometheus connector that was installed'
13+
GET STACKED DIAGNOSTICS original_message = MESSAGE_TEXT;
14+
RAISE EXCEPTION 'could not determine the version of the timescale-prometheus connector that was installed due to: %', original_message
1315
USING HINT='This extension should not be created manually. It will be created by the timescale-prometheus connector and requires the connector to be installed first.';
1416
RETURN;
17+
END;
1518

16-
IF version < 1 OR DIRTY THEN
19+
IF current_version < 1 OR is_dirty THEN
1720
RAISE EXCEPTION 'the requisite version of the timescale-prometheus connector has not been installed'
1821
USING HINT='This extension should not be created manually. It will be created by the timescale-prometheus connector and requires the connector to be installed first.';
1922
END IF;
2023
END
2124
$$;
2225

23-
SET LOCAL search_path TO DEFAULT;
26+
-- Set the search path to one that will find all the definitions provided by the
27+
-- connector. Since the connector can change the schemas it stores things
28+
-- in we cannot just hardcode the searchpath, instead we switch the search path
29+
-- based on the schemas declared by the connector.
30+
DO $$
31+
DECLARE
32+
ext_schema TEXT;
33+
prom_schema TEXT;
34+
metric_schema TEXT;
35+
catalog_schema TEXT;
36+
new_path TEXT;
37+
BEGIN
38+
SELECT value FROM public.prom_installation_info
39+
WHERE key = 'extension schema'
40+
INTO ext_schema;
41+
SELECT value FROM public.prom_installation_info
42+
WHERE key = 'prometheus API schema'
43+
INTO prom_schema;
44+
SELECT value FROM public.prom_installation_info
45+
WHERE key = 'catalog schema'
46+
INTO catalog_schema;
47+
new_path := format('public,%s,%s,%s', ext_schema, prom_schema, catalog_schema);
48+
PERFORM set_config('search_path', new_path, false);
49+
END
50+
$$;
2451

2552
DO $$
2653
BEGIN
@@ -56,48 +83,48 @@ GRANT EXECUTE ON FUNCTION @[email protected]_unnest(anyarray) TO prom_reader;
5683

5784
--------------------- comparison functions ---------------------
5885

59-
CREATE OR REPLACE FUNCTION @[email protected]_find_key_equal(label_key label_key, pattern pattern)
86+
CREATE OR REPLACE FUNCTION @[email protected]_find_key_equal(key label_key, pat pattern)
6087
RETURNS matcher_positive
6188
AS $func$
6289
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_positive
63-
FROM _prom_catalog.label l
64-
WHERE l.key = label_key and l.value = pattern
90+
FROM label l
91+
WHERE l.key = key and l.value = pat
6592
$func$
6693
LANGUAGE SQL STABLE PARALLEL SAFE
67-
SUPPORT const_support;
94+
SUPPORT @extschema@.const_support;
6895
GRANT EXECUTE ON FUNCTION @[email protected]_find_key_equal(label_key, pattern) TO prom_reader;
6996

70-
CREATE OR REPLACE FUNCTION @[email protected]_find_key_not_equal(key label_key, pattern pattern)
97+
CREATE OR REPLACE FUNCTION @[email protected]_find_key_not_equal(key label_key, pat pattern)
7198
RETURNS matcher_negative
7299
AS $func$
73100
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_negative
74-
FROM _prom_catalog.label l
75-
WHERE l.key = key and l.value = pattern
101+
FROM label l
102+
WHERE l.key = key and l.value = pat
76103
$func$
77104
LANGUAGE SQL STABLE PARALLEL SAFE
78-
SUPPORT const_support;
105+
SUPPORT @extschema@.const_support;
79106
GRANT EXECUTE ON FUNCTION @[email protected]_find_key_not_equal(label_key, pattern) TO prom_reader;
80107

81-
CREATE OR REPLACE FUNCTION @[email protected]_find_key_regex(key label_key, pattern pattern)
108+
CREATE OR REPLACE FUNCTION @[email protected]_find_key_regex(key label_key, pat pattern)
82109
RETURNS matcher_positive
83110
AS $func$
84111
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_positive
85-
FROM _prom_catalog.label l
86-
WHERE l.key = key and l.value ~ pattern
112+
FROM label l
113+
WHERE l.key = key and l.value ~ pat
87114
$func$
88115
LANGUAGE SQL STABLE PARALLEL SAFE
89-
SUPPORT const_support;
116+
SUPPORT @extschema@.const_support;
90117
GRANT EXECUTE ON FUNCTION @[email protected]_find_key_regex(label_key, pattern) TO prom_reader;
91118

92-
CREATE OR REPLACE FUNCTION @[email protected]_find_key_not_regex(key label_key, pattern pattern)
119+
CREATE OR REPLACE FUNCTION @[email protected]_find_key_not_regex(key label_key, pat pattern)
93120
RETURNS matcher_negative
94121
AS $func$
95122
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_negative
96-
FROM _prom_catalog.label l
97-
WHERE l.key = key and l.value ~ pattern
123+
FROM label l
124+
WHERE l.key = key and l.value ~ pat
98125
$func$
99126
LANGUAGE SQL STABLE PARALLEL SAFE
100-
SUPPORT const_support;
127+
SUPPORT @extschema@.const_support;
101128
GRANT EXECUTE ON FUNCTION @[email protected]_find_key_not_regex(label_key, pattern) TO prom_reader;
102129

103130
CREATE OPERATOR @extschema@.== (

pkg/internal/testhelpers/containers.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,27 @@ import (
1010
"io/ioutil"
1111
"runtime"
1212

13+
"os"
14+
"path/filepath"
15+
"testing"
16+
1317
"github.com/docker/go-connections/nat"
1418
"github.com/jackc/pgx/v4"
1519
"github.com/jackc/pgx/v4/pgxpool"
1620
_ "github.com/jackc/pgx/v4/stdlib"
1721
"github.com/testcontainers/testcontainers-go"
1822
"github.com/testcontainers/testcontainers-go/wait"
19-
"os"
20-
"path/filepath"
21-
"testing"
2223
)
2324

2425
const (
2526
defaultDB = "postgres"
26-
connectTemplate = "postgres://postgres:password@%s:%d/%s"
27+
connectTemplate = "postgres://%s:password@%s:%d/%s"
28+
29+
postgresUser = "postgres"
30+
promUser = "prom"
31+
32+
Superuser = true
33+
NoSuperuser = false
2734
)
2835

2936
var (
@@ -34,25 +41,31 @@ var (
3441
pgPort nat.Port = "5432/tcp"
3542
)
3643

37-
func pgConnectURL(dbName string) string {
38-
return fmt.Sprintf(connectTemplate, pgHost, pgPort.Int(), dbName)
44+
type SuperuserStatus = bool
45+
46+
func pgConnectURL(dbName string, superuser SuperuserStatus) string {
47+
user := postgresUser
48+
if !superuser {
49+
user = promUser
50+
}
51+
return fmt.Sprintf(connectTemplate, user, pgHost, pgPort.Int(), dbName)
3952
}
4053

4154
// WithDB establishes a database for testing and calls the callback
42-
func WithDB(t testing.TB, DBName string, f func(db *pgxpool.Pool, t testing.TB, connectString string)) {
43-
db, err := dbSetup(DBName)
55+
func WithDB(t testing.TB, DBName string, superuser SuperuserStatus, f func(db *pgxpool.Pool, t testing.TB, connectString string)) {
56+
db, err := dbSetup(DBName, superuser)
4457
if err != nil {
4558
t.Fatal(err)
4659
return
4760
}
4861
defer func() {
4962
db.Close()
5063
}()
51-
f(db, t, pgConnectURL(DBName))
64+
f(db, t, pgConnectURL(DBName, superuser))
5265
}
5366

5467
func GetReadOnlyConnection(t testing.TB, DBName string) *pgxpool.Pool {
55-
dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName))
68+
dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName, NoSuperuser))
5669
if err != nil {
5770
t.Fatal(err)
5871
}
@@ -65,8 +78,8 @@ func GetReadOnlyConnection(t testing.TB, DBName string) *pgxpool.Pool {
6578
return dbPool
6679
}
6780

68-
func dbSetup(DBName string) (*pgxpool.Pool, error) {
69-
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB))
81+
func dbSetup(DBName string, superuser SuperuserStatus) (*pgxpool.Pool, error) {
82+
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB, Superuser))
7083
if err != nil {
7184
return nil, err
7285
}
@@ -76,7 +89,7 @@ func dbSetup(DBName string) (*pgxpool.Pool, error) {
7689
return nil, err
7790
}
7891

79-
_, err = db.Exec(context.Background(), fmt.Sprintf("CREATE DATABASE %s", DBName))
92+
_, err = db.Exec(context.Background(), fmt.Sprintf("CREATE DATABASE %s OWNER %s", DBName, promUser))
8093
if err != nil {
8194
return nil, err
8295
}
@@ -86,7 +99,7 @@ func dbSetup(DBName string) (*pgxpool.Pool, error) {
8699
return nil, err
87100
}
88101

89-
dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName))
102+
dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName, superuser))
90103
if err != nil {
91104
return nil, err
92105
}
@@ -138,6 +151,21 @@ func StartPGContainer(ctx context.Context, withExtension bool, testDataDir strin
138151
return nil, err
139152
}
140153

154+
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB, Superuser))
155+
if err != nil {
156+
return nil, err
157+
}
158+
159+
_, err = db.Exec(context.Background(), fmt.Sprintf("CREATE USER %s WITH NOSUPERUSER CREATEROLE PASSWORD 'password'", promUser))
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
err = db.Close(context.Background())
165+
if err != nil {
166+
return nil, err
167+
}
168+
141169
return container, nil
142170
}
143171

pkg/internal/testhelpers/containers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestPGConnection(t *testing.T) {
2727
if testing.Short() {
2828
t.Skip("skipping integration test")
2929
}
30-
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB))
30+
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB, Superuser))
3131
if err != nil {
3232
t.Fatal(err)
3333
}
@@ -46,7 +46,7 @@ func TestWithDB(t *testing.T) {
4646
if testing.Short() {
4747
t.Skip("skipping integration test")
4848
}
49-
WithDB(t, *database, func(db *pgxpool.Pool, t testing.TB, connectURL string) {
49+
WithDB(t, *database, Superuser, func(db *pgxpool.Pool, t testing.TB, connectURL string) {
5050
var res int
5151
err := db.QueryRow(context.Background(), "SELECT 1").Scan(&res)
5252
if err != nil {

pkg/pgmodel/migrate.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import (
2121
)
2222

2323
const (
24-
extensionInstall = `CREATE EXTENSION "timescale_prometheus_extra" SCHEMA %s;
25-
`
24+
timescaleInstall = "CREATE EXTENSION IF NOT EXISTS timescaledb WITH SCHEMA public;"
25+
extensionInstall = "CREATE EXTENSION timescale_prometheus_extra WITH SCHEMA %s;"
2626
)
2727

2828
type mySrc struct {
@@ -73,9 +73,14 @@ func (t *mySrc) ReadDown(version uint) (r io.ReadCloser, identifier string, err
7373
func Migrate(db *sql.DB) error {
7474
// The migration table will be put in the public schema not in any of our schema because we never want to drop it and
7575
// our scripts and our last down script drops our shemas
76-
driver, err := postgres.WithInstance(db, &postgres.Config{MigrationsTable: fmt.Sprintf("%s_schema_migrations", promSchema)})
76+
driver, err := postgres.WithInstance(db, &postgres.Config{MigrationsTable: "prom_schema_migrations"})
7777
if err != nil {
78-
return err
78+
return fmt.Errorf("cannot create driver due to %w", err)
79+
}
80+
81+
_, err = db.Exec(timescaleInstall)
82+
if err != nil {
83+
return fmt.Errorf("timescaledb failed to install due to %w", err)
7984
}
8085

8186
src, err := httpfs.New(migrations.SqlFiles, "/")

0 commit comments

Comments
 (0)