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

Commit

Permalink
Allow timescale_prometheus_extra installation without superuser
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JLockerman committed May 5, 2020
1 parent 9a737ae commit d9e4f17
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 89 deletions.
12 changes: 12 additions & 0 deletions extension/003-enable-pgextwlist.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -ex

sed -i -e "s/#shared_preload_libraries/shared_preload_libraries/" \
/var/lib/postgresql/data/postgresql.conf \

sed -i \
-e "s/shared_preload_libraries = '/shared_preload_libraries = 'pgextwlist,/" \
/var/lib/postgresql/data/postgresql.conf

echo "extwlist.extensions = 'timescale_prometheus_extra,timescaledb'" >> \
/var/lib/postgresql/data/postgresql.conf
26 changes: 19 additions & 7 deletions extension/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ FROM timescale/timescaledb:${TIMESCALEDB_VERSION}-${PG_VERSION_TAG}

MAINTAINER Timescale https://www.timescale.com

COPY timescale_prometheus_extra.control Makefile /build/timescale-prometheus/
COPY src/*.c src/*.h /build/timescale-prometheus/src/
COPY sql/timescale-prometheus.sql /build/timescale-prometheus/sql/

RUN set -ex \
&& apk add --no-cache --virtual .build-deps \
coreutils \
Expand All @@ -16,10 +12,26 @@ RUN set -ex \
libc-dev \
make \
util-linux-dev \
clang \
llvm \
\
clang \
llvm \
git
RUN set -ex \
&& git clone --branch v1.9 --depth 1 \
https://github.com/dimitri/pgextwlist.git /pgextwlist \
&& cd /pgextwlist \
&& make \
&& make install \
&& cp /pgextwlist/pgextwlist.so `pg_config --pkglibdir`/plugins \
&& rm -rf /pgextwlist

COPY timescale_prometheus_extra.control Makefile /build/timescale-prometheus/
COPY src/*.c src/*.h /build/timescale-prometheus/src/
COPY sql/timescale-prometheus.sql /build/timescale-prometheus/sql/

RUN set -ex \
&& make -C /build/timescale-prometheus install \
\
&& apk del .build-deps \
&& rm -rf /build

COPY 003-enable-pgextwlist.sh /docker-entrypoint-initdb.d/
73 changes: 50 additions & 23 deletions extension/sql/timescale-prometheus.sql
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@

DO $$
DECLARE
version INT;
dirty BOOLEAN;
current_version INT;
is_dirty BOOLEAN;
original_message TEXT;
BEGIN
BEGIN
SELECT version, dirty
INTO STRICT version, dirty
FROM public.prom_schema_migrations;
INTO STRICT current_version, is_dirty
FROM public.prom_schema_migrations;
EXCEPTION WHEN OTHERS THEN
RAISE EXCEPTION 'could not determine the version of the timescale-prometheus connector that was installed'
GET STACKED DIAGNOSTICS original_message = MESSAGE_TEXT;
RAISE EXCEPTION 'could not determine the version of the timescale-prometheus connector that was installed due to: %', original_message
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.';
RETURN;
END;

IF version < 1 OR DIRTY THEN
IF current_version < 1 OR is_dirty THEN
RAISE EXCEPTION 'the requisite version of the timescale-prometheus connector has not been installed'
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.';
END IF;
END
$$;

SET LOCAL search_path TO DEFAULT;
-- Set the search path to one that will find all the definitions provided by the
-- connector. Since the connector can change the schemas it stores things
-- in we cannot just hardcode the searchpath, instead we switch the search path
-- based on the schemas declared by the connector.
DO $$
DECLARE
ext_schema TEXT;
prom_schema TEXT;
metric_schema TEXT;
catalog_schema TEXT;
new_path TEXT;
BEGIN
SELECT value FROM public.prom_installation_info
WHERE key = 'extension schema'
INTO ext_schema;
SELECT value FROM public.prom_installation_info
WHERE key = 'prometheus API schema'
INTO prom_schema;
SELECT value FROM public.prom_installation_info
WHERE key = 'catalog schema'
INTO catalog_schema;
new_path := format('public,%s,%s,%s', ext_schema, prom_schema, catalog_schema);
PERFORM set_config('search_path', new_path, false);
END
$$;

DO $$
BEGIN
Expand Down Expand Up @@ -56,48 +83,48 @@ GRANT EXECUTE ON FUNCTION @extschema@.label_unnest(anyarray) TO prom_reader;

--------------------- comparison functions ---------------------

CREATE OR REPLACE FUNCTION @extschema@.label_find_key_equal(label_key label_key, pattern pattern)
CREATE OR REPLACE FUNCTION @extschema@.label_find_key_equal(key label_key, pat pattern)
RETURNS matcher_positive
AS $func$
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_positive
FROM _prom_catalog.label l
WHERE l.key = label_key and l.value = pattern
FROM label l
WHERE l.key = key and l.value = pat
$func$
LANGUAGE SQL STABLE PARALLEL SAFE
SUPPORT const_support;
SUPPORT @extschema@.const_support;
GRANT EXECUTE ON FUNCTION @extschema@.label_find_key_equal(label_key, pattern) TO prom_reader;

CREATE OR REPLACE FUNCTION @extschema@.label_find_key_not_equal(key label_key, pattern pattern)
CREATE OR REPLACE FUNCTION @extschema@.label_find_key_not_equal(key label_key, pat pattern)
RETURNS matcher_negative
AS $func$
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_negative
FROM _prom_catalog.label l
WHERE l.key = key and l.value = pattern
FROM label l
WHERE l.key = key and l.value = pat
$func$
LANGUAGE SQL STABLE PARALLEL SAFE
SUPPORT const_support;
SUPPORT @extschema@.const_support;
GRANT EXECUTE ON FUNCTION @extschema@.label_find_key_not_equal(label_key, pattern) TO prom_reader;

CREATE OR REPLACE FUNCTION @extschema@.label_find_key_regex(key label_key, pattern pattern)
CREATE OR REPLACE FUNCTION @extschema@.label_find_key_regex(key label_key, pat pattern)
RETURNS matcher_positive
AS $func$
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_positive
FROM _prom_catalog.label l
WHERE l.key = key and l.value ~ pattern
FROM label l
WHERE l.key = key and l.value ~ pat
$func$
LANGUAGE SQL STABLE PARALLEL SAFE
SUPPORT const_support;
SUPPORT @extschema@.const_support;
GRANT EXECUTE ON FUNCTION @extschema@.label_find_key_regex(label_key, pattern) TO prom_reader;

CREATE OR REPLACE FUNCTION @extschema@.label_find_key_not_regex(key label_key, pattern pattern)
CREATE OR REPLACE FUNCTION @extschema@.label_find_key_not_regex(key label_key, pat pattern)
RETURNS matcher_negative
AS $func$
SELECT COALESCE(array_agg(l.id), array[]::int[])::matcher_negative
FROM _prom_catalog.label l
WHERE l.key = key and l.value ~ pattern
FROM label l
WHERE l.key = key and l.value ~ pat
$func$
LANGUAGE SQL STABLE PARALLEL SAFE
SUPPORT const_support;
SUPPORT @extschema@.const_support;
GRANT EXECUTE ON FUNCTION @extschema@.label_find_key_not_regex(label_key, pattern) TO prom_reader;

CREATE OPERATOR @extschema@.== (
Expand Down
56 changes: 42 additions & 14 deletions pkg/internal/testhelpers/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@ import (
"io/ioutil"
"runtime"

"os"
"path/filepath"
"testing"

"github.com/docker/go-connections/nat"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"os"
"path/filepath"
"testing"
)

const (
defaultDB = "postgres"
connectTemplate = "postgres://postgres:password@%s:%d/%s"
connectTemplate = "postgres://%s:password@%s:%d/%s"

postgresUser = "postgres"
promUser = "prom"

Superuser = true
NoSuperuser = false
)

var (
Expand All @@ -34,25 +41,31 @@ var (
pgPort nat.Port = "5432/tcp"
)

func pgConnectURL(dbName string) string {
return fmt.Sprintf(connectTemplate, pgHost, pgPort.Int(), dbName)
type SuperuserStatus = bool

func pgConnectURL(dbName string, superuser SuperuserStatus) string {
user := postgresUser
if !superuser {
user = promUser
}
return fmt.Sprintf(connectTemplate, user, pgHost, pgPort.Int(), dbName)
}

// WithDB establishes a database for testing and calls the callback
func WithDB(t testing.TB, DBName string, f func(db *pgxpool.Pool, t testing.TB, connectString string)) {
db, err := dbSetup(DBName)
func WithDB(t testing.TB, DBName string, superuser SuperuserStatus, f func(db *pgxpool.Pool, t testing.TB, connectString string)) {
db, err := dbSetup(DBName, superuser)
if err != nil {
t.Fatal(err)
return
}
defer func() {
db.Close()
}()
f(db, t, pgConnectURL(DBName))
f(db, t, pgConnectURL(DBName, superuser))
}

func GetReadOnlyConnection(t testing.TB, DBName string) *pgxpool.Pool {
dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName))
dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName, NoSuperuser))
if err != nil {
t.Fatal(err)
}
Expand All @@ -65,8 +78,8 @@ func GetReadOnlyConnection(t testing.TB, DBName string) *pgxpool.Pool {
return dbPool
}

func dbSetup(DBName string) (*pgxpool.Pool, error) {
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB))
func dbSetup(DBName string, superuser SuperuserStatus) (*pgxpool.Pool, error) {
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB, Superuser))
if err != nil {
return nil, err
}
Expand All @@ -76,7 +89,7 @@ func dbSetup(DBName string) (*pgxpool.Pool, error) {
return nil, err
}

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

dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName))
dbPool, err := pgxpool.Connect(context.Background(), pgConnectURL(DBName, superuser))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -138,6 +151,21 @@ func StartPGContainer(ctx context.Context, withExtension bool, testDataDir strin
return nil, err
}

db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB, Superuser))
if err != nil {
return nil, err
}

_, err = db.Exec(context.Background(), fmt.Sprintf("CREATE USER %s WITH NOSUPERUSER CREATEROLE PASSWORD 'password'", promUser))
if err != nil {
return nil, err
}

err = db.Close(context.Background())
if err != nil {
return nil, err
}

return container, nil
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/internal/testhelpers/containers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestPGConnection(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB))
db, err := pgx.Connect(context.Background(), pgConnectURL(defaultDB, Superuser))
if err != nil {
t.Fatal(err)
}
Expand All @@ -46,7 +46,7 @@ func TestWithDB(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
WithDB(t, *database, func(db *pgxpool.Pool, t testing.TB, connectURL string) {
WithDB(t, *database, Superuser, func(db *pgxpool.Pool, t testing.TB, connectURL string) {
var res int
err := db.QueryRow(context.Background(), "SELECT 1").Scan(&res)
if err != nil {
Expand Down
13 changes: 9 additions & 4 deletions pkg/pgmodel/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import (
)

const (
extensionInstall = `CREATE EXTENSION "timescale_prometheus_extra" SCHEMA %s;
`
timescaleInstall = "CREATE EXTENSION IF NOT EXISTS timescaledb WITH SCHEMA public;"
extensionInstall = "CREATE EXTENSION timescale_prometheus_extra WITH SCHEMA %s;"
)

type mySrc struct {
Expand Down Expand Up @@ -73,9 +73,14 @@ func (t *mySrc) ReadDown(version uint) (r io.ReadCloser, identifier string, err
func Migrate(db *sql.DB) error {
// The migration table will be put in the public schema not in any of our schema because we never want to drop it and
// our scripts and our last down script drops our shemas
driver, err := postgres.WithInstance(db, &postgres.Config{MigrationsTable: fmt.Sprintf("%s_schema_migrations", promSchema)})
driver, err := postgres.WithInstance(db, &postgres.Config{MigrationsTable: "prom_schema_migrations"})
if err != nil {
return err
return fmt.Errorf("cannot create driver due to %w", err)
}

_, err = db.Exec(timescaleInstall)
if err != nil {
return fmt.Errorf("timescaledb failed to install due to %w", err)
}

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

0 comments on commit d9e4f17

Please sign in to comment.