chore: update migrations & images and switch to new registry
This commit is contained in:
parent
87a06dac66
commit
b4347cc8a2
23 changed files with 388 additions and 37 deletions
.github/workflows
.goreleaser.yaml.husky.toml.zed
Tiltfileassets/migrations
init-scripts
migrations
config
control-plane
dev
manager
release/default
hack
internal
controller
db
supabase
magefiles
testdata/dotnet-client/test/supabase-integration.api-test
2
.github/workflows/postgres.yml
vendored
2
.github/workflows/postgres.yml
vendored
|
@ -13,7 +13,7 @@ on:
|
||||||
- "v*"
|
- "v*"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MINOR_VERSIONS: '{"15": "12","17": "4"}'
|
MINOR_VERSIONS: '{"15":"12","17":"4"}'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -22,9 +22,9 @@ jobs:
|
||||||
- name: Login to container registry
|
- name: Login to container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: code.icb4dc0.de
|
registry: registry.icb4dc0.de
|
||||||
username: prskr
|
username: ${{ secrets.HARBOR_USER }}
|
||||||
password: ${{ secrets.RELEASE_TOKEN }}
|
password: ${{ secrets.HARBOR_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
|
|
|
@ -8,7 +8,7 @@ before:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
- go run mage.go GenerateAll
|
- go run mage.go GenerateAll
|
||||||
- mkdir -p out
|
- mkdir -p out
|
||||||
- sh -c "cd config/release/default && kustomize edit set image supabase-operator=code.icb4dc0.de/prskr/supabase-operator:{{.Version}}"
|
- sh -c "cd config/release/default && kustomize edit set image supabase-operator=registry.icb4dc0.de/supabase-operator/controller:{{.Version}}"
|
||||||
- sh -c "kustomize build config/release/default > out/operator_manifest.yaml"
|
- sh -c "kustomize build config/release/default > out/operator_manifest.yaml"
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
|
@ -28,7 +28,7 @@ kos:
|
||||||
base_image: gcr.io/distroless/static:nonroot
|
base_image: gcr.io/distroless/static:nonroot
|
||||||
user: "65532:65532"
|
user: "65532:65532"
|
||||||
repositories:
|
repositories:
|
||||||
- code.icb4dc0.de/prskr/supabase-operator
|
- registry.icb4dc0.de/supabase-operator/controller
|
||||||
platforms:
|
platforms:
|
||||||
- linux/amd64
|
- linux/amd64
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# git hook pre commit
|
# git hook pre commit
|
||||||
pre-commit = [
|
pre-commit = [
|
||||||
"go mod tidy -go=1.24",
|
"go mod tidy -go=1.24",
|
||||||
"go run mage.go GenerateAll",
|
|
||||||
"husky lint-staged",
|
"husky lint-staged",
|
||||||
# "golangci-lint run",
|
# "golangci-lint run",
|
||||||
]
|
]
|
||||||
|
|
47
.zed/tasks.json
Normal file
47
.zed/tasks.json
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Tilt Up",
|
||||||
|
"command": "tilt",
|
||||||
|
"args": ["up"],
|
||||||
|
"use_new_terminal": false,
|
||||||
|
"allow_concurrent_runs": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Generate CRDs",
|
||||||
|
"command": "go",
|
||||||
|
"args": ["run", "mage.go", "CRDs"],
|
||||||
|
"use_new_terminal": false,
|
||||||
|
"allow_concurrent_runs": false,
|
||||||
|
"tags": ["mage", "generate"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Generate CRD docs",
|
||||||
|
"command": "go",
|
||||||
|
"args": ["run", "mage.go", "CRDDocs"],
|
||||||
|
"use_new_terminal": false,
|
||||||
|
"allow_concurrent_runs": false,
|
||||||
|
"tags": ["mage", "generate"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Update image meta",
|
||||||
|
"command": "go",
|
||||||
|
"args": ["run", "mage.go", "FetchImageMeta"],
|
||||||
|
"use_new_terminal": false,
|
||||||
|
"allow_concurrent_runs": false,
|
||||||
|
"tags": ["mage"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Update DB migrations",
|
||||||
|
"command": "go",
|
||||||
|
"args": ["run", "mage.go", "FetchMigrations"],
|
||||||
|
"use_new_terminal": false,
|
||||||
|
"allow_concurrent_runs": false,
|
||||||
|
"tags": ["mage"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run all Go tests",
|
||||||
|
"command": "go tool gotestsum ./...",
|
||||||
|
"use_new_terminal": false,
|
||||||
|
"allow_concurrent_runs": false
|
||||||
|
}
|
||||||
|
]
|
2
Tiltfile
2
Tiltfile
|
@ -25,7 +25,7 @@ local_resource(
|
||||||
k8s_kind('Cluster', api_version='postgresql.cnpg.io/v1')
|
k8s_kind('Cluster', api_version='postgresql.cnpg.io/v1')
|
||||||
|
|
||||||
docker_build_with_restart(
|
docker_build_with_restart(
|
||||||
'supabase-operator',
|
'controller',
|
||||||
'.',
|
'.',
|
||||||
entrypoint=['/app/bin/supabase-operator'],
|
entrypoint=['/app/bin/supabase-operator'],
|
||||||
dockerfile='dev/Dockerfile',
|
dockerfile='dev/Dockerfile',
|
||||||
|
|
|
@ -18,7 +18,15 @@ grant pg_read_all_data to supabase_read_only_user;
|
||||||
create schema if not exists extensions;
|
create schema if not exists extensions;
|
||||||
create extension if not exists "uuid-ossp" with schema extensions;
|
create extension if not exists "uuid-ossp" with schema extensions;
|
||||||
create extension if not exists pgcrypto with schema extensions;
|
create extension if not exists pgcrypto with schema extensions;
|
||||||
create extension if not exists pgjwt with schema extensions;
|
do $$
|
||||||
|
begin
|
||||||
|
if exists (select 1 from pg_available_extensions where name = 'pgjwt') then
|
||||||
|
if not exists (select 1 from pg_extension where extname = 'pgjwt') then
|
||||||
|
create extension if not exists pgjwt with schema "extensions" cascade;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
|
||||||
-- Set up auth roles for the developer
|
-- Set up auth roles for the developer
|
||||||
create role anon nologin noinherit;
|
create role anon nologin noinherit;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
-- migrate:up
|
||||||
|
do $$
|
||||||
|
declare
|
||||||
|
ext_schema text;
|
||||||
|
extensions_schema_exists boolean;
|
||||||
|
begin
|
||||||
|
-- check if the "extensions" schema exists
|
||||||
|
select exists (
|
||||||
|
select 1 from pg_namespace where nspname = 'extensions'
|
||||||
|
) into extensions_schema_exists;
|
||||||
|
|
||||||
|
if extensions_schema_exists then
|
||||||
|
-- check if the "orioledb" extension is in the "public" schema
|
||||||
|
select nspname into ext_schema
|
||||||
|
from pg_extension e
|
||||||
|
join pg_namespace n on e.extnamespace = n.oid
|
||||||
|
where extname = 'orioledb';
|
||||||
|
|
||||||
|
if ext_schema = 'public' then
|
||||||
|
execute 'alter extension orioledb set schema extensions';
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
-- migrate:down
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
-- migrate:up
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT FROM pg_extension WHERE extname = 'pgsodium') THEN
|
||||||
|
CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path TO ''
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
EXECUTE format(
|
||||||
|
'GRANT SELECT ON pgsodium.key TO %s',
|
||||||
|
masked_role);
|
||||||
|
|
||||||
|
EXECUTE format(
|
||||||
|
'GRANT pgsodium_keyiduser, pgsodium_keyholder TO %s',
|
||||||
|
masked_role);
|
||||||
|
|
||||||
|
EXECUTE format(
|
||||||
|
'GRANT ALL ON %I TO %s',
|
||||||
|
view_name,
|
||||||
|
masked_role);
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
$function$;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- migrate:down
|
|
@ -0,0 +1,64 @@
|
||||||
|
-- migrate:up
|
||||||
|
CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
|
||||||
|
RETURNS event_trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_event_trigger_ddl_commands() AS ev
|
||||||
|
JOIN pg_extension AS ext
|
||||||
|
ON ev.objid = ext.oid
|
||||||
|
WHERE ext.extname = 'pg_net'
|
||||||
|
)
|
||||||
|
THEN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_roles
|
||||||
|
WHERE rolname = 'supabase_functions_admin'
|
||||||
|
)
|
||||||
|
THEN
|
||||||
|
CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||||
|
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT FROM pg_extension
|
||||||
|
WHERE extname = 'pg_net'
|
||||||
|
-- all versions in use on existing projects as of 2025-02-20
|
||||||
|
-- version 0.12.0 onwards don't need these applied
|
||||||
|
AND extversion IN ('0.2', '0.6', '0.7', '0.7.1', '0.8', '0.10.0', '0.11.0')
|
||||||
|
) THEN
|
||||||
|
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||||
|
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||||
|
|
||||||
|
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||||
|
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||||
|
|
||||||
|
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||||
|
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||||
|
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT FROM pg_extension WHERE extname = 'pg_net')
|
||||||
|
THEN
|
||||||
|
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY INVOKER;
|
||||||
|
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY INVOKER;
|
||||||
|
|
||||||
|
REVOKE EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||||
|
REVOKE EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||||
|
|
||||||
|
GRANT ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO PUBLIC;
|
||||||
|
GRANT ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO PUBLIC;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- migrate:down
|
|
@ -25,7 +25,7 @@ spec:
|
||||||
containers:
|
containers:
|
||||||
- args:
|
- args:
|
||||||
- control-plane
|
- control-plane
|
||||||
image: supabase-operator:latest
|
image: controller:latest
|
||||||
name: control-plane
|
name: control-plane
|
||||||
env:
|
env:
|
||||||
- name: CONTROL_PLANE_NAMESPACE
|
- name: CONTROL_PLANE_NAMESPACE
|
||||||
|
|
|
@ -44,7 +44,7 @@ metadata:
|
||||||
namespace: supabase-demo
|
namespace: supabase-demo
|
||||||
spec:
|
spec:
|
||||||
instances: 1
|
instances: 1
|
||||||
imageName: code.icb4dc0.de/prskr/supabase-operator/postgres:17.2
|
imageName: registry.icb4dc0.de/supabase-operator/postgres:17.4
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
postgresUID: 26
|
postgresUID: 26
|
||||||
postgresGID: 102
|
postgresGID: 102
|
||||||
|
|
|
@ -29,7 +29,7 @@ spec:
|
||||||
- manager
|
- manager
|
||||||
- --leader-elect
|
- --leader-elect
|
||||||
- --health-probe-bind-address=:8081
|
- --health-probe-bind-address=:8081
|
||||||
image: supabase-operator:latest
|
image: controller:latest
|
||||||
name: manager
|
name: manager
|
||||||
env:
|
env:
|
||||||
- name: CONTROLLER_NAMESPACE
|
- name: CONTROLLER_NAMESPACE
|
||||||
|
|
|
@ -2,9 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
|
|
||||||
images:
|
images:
|
||||||
- name: supabase-operator
|
- name: controller
|
||||||
newName: code.icb4dc0.de/prskr/supabase-operator
|
newName: registry.icb4dc0.de/supabase-operator/controller
|
||||||
newTag: 0.1.0-SNAPSHOT-e6c7d68
|
newTag: 0.1.0-SNAPSHOT-e6c7d68
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ../../default
|
- ../../default
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export DATABASE_URL="postgres://supabase_admin:1n1t-R00t!@localhost:5432/app"
|
|
||||||
|
|
||||||
go run mage.go Migrate
|
|
16
internal/controller/common_test.go
Normal file
16
internal/controller/common_test.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package controller_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
|
|
||||||
|
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testingScheme = runtime.NewScheme()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utilruntime.Must(clientgoscheme.AddToScheme(testingScheme))
|
||||||
|
utilruntime.Must(supabasev1alpha1.AddToScheme(testingScheme))
|
||||||
|
}
|
81
internal/controller/storage_s3_creds_controller_test.go
Normal file
81
internal/controller/storage_s3_creds_controller_test.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package controller_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
|
||||||
|
supabasev1alpha1 "code.icb4dc0.de/prskr/supabase-operator/api/v1alpha1"
|
||||||
|
"code.icb4dc0.de/prskr/supabase-operator/internal/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStorageS3CredsController(t *testing.T) {
|
||||||
|
storage := new(supabasev1alpha1.Storage)
|
||||||
|
|
||||||
|
MustReadObject(t, filepath.Join("testdata", "storage_basic.yaml"), storage)
|
||||||
|
|
||||||
|
t.Log(string(MustMarshal(t, storage)))
|
||||||
|
|
||||||
|
clientSet := fake.NewClientBuilder().
|
||||||
|
WithObjects(storage).
|
||||||
|
WithScheme(testingScheme).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
log.SetLogger(zap.New(zap.UseDevMode(true)))
|
||||||
|
|
||||||
|
controllerReconciler := &controller.StorageS3CredentialsReconciler{
|
||||||
|
Client: clientSet,
|
||||||
|
Scheme: testingScheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := controllerReconciler.Reconcile(t.Context(), reconcile.Request{
|
||||||
|
NamespacedName: client.ObjectKeyFromObject(storage),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustMarshal(t testing.TB, obj runtime.Object) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
raw, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to marshal object: %v", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustReadObject(t testing.TB, relativePath string, obj runtime.Object) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
gkvs, _, err := testingScheme.ObjectKinds(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to determine group version kind: %v", err)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := scheme.Codecs.UniversalDecoder(testingScheme.PreferredVersionAllGroups()...)
|
||||||
|
|
||||||
|
raw, err := os.ReadFile(relativePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to read file %s: %v", relativePath, err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = decoder.Decode(raw, &gkvs[0], obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to unmarshal file %s: %v", relativePath, err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
28
internal/controller/testdata/storage_basic.yaml
vendored
Normal file
28
internal/controller/testdata/storage_basic.yaml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
apiVersion: supabase.k8s.icb4dc0.de/v1alpha1
|
||||||
|
kind: Storage
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: supabase-operator
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: storage-sample
|
||||||
|
namespace: supabase-demo
|
||||||
|
spec:
|
||||||
|
api:
|
||||||
|
s3:
|
||||||
|
credentialsSecretRef:
|
||||||
|
secretName: "storage-api-s3-credentials"
|
||||||
|
accessKeyIdKey: accessKeyId
|
||||||
|
accessSecretKeyKey: secretAccessKey
|
||||||
|
db:
|
||||||
|
host: cluster-example-rw.supabase-demo.svc
|
||||||
|
dbName: app
|
||||||
|
dbCredentialsRef:
|
||||||
|
# will be created by Core resource operator if not present
|
||||||
|
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||||
|
# format <core-resource-name>-db-creds-supabase-storage-admin
|
||||||
|
secretName: core-sample-db-creds-supabase-storage-admin
|
||||||
|
jwtAuth:
|
||||||
|
# will be created by Core resource operator if not present
|
||||||
|
# just make sure the secret name is either based on the name of the core resource or explicitly set
|
||||||
|
secretName: core-sample-jwt
|
|
@ -55,7 +55,8 @@ func (m Migrator) ApplyAll(
|
||||||
logger.Info("Applying missing or outdated migration", "filename", s.FileName)
|
logger.Info("Applying missing or outdated migration", "filename", s.FileName)
|
||||||
err := status.Database.RecordMigrationCondition(s.FileName, s.Hash, m.Apply(ctx, s.Content))
|
err := status.Database.RecordMigrationCondition(s.FileName, s.Hash, m.Apply(ctx, s.Content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
logger.Error(err, "Failed to apply migrations", "filename", s.FileName)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
appliedSomething = true
|
appliedSomething = true
|
||||||
|
|
|
@ -37,7 +37,7 @@ type studioDefaults struct {
|
||||||
func studioServiceConfig() serviceConfig[studioEnvKeys, studioDefaults] {
|
func studioServiceConfig() serviceConfig[studioEnvKeys, studioDefaults] {
|
||||||
return serviceConfig[studioEnvKeys, studioDefaults]{
|
return serviceConfig[studioEnvKeys, studioDefaults]{
|
||||||
Name: "studio",
|
Name: "studio",
|
||||||
LivenessProbePath: "/api/profile",
|
LivenessProbePath: "/api/platform/profile",
|
||||||
EnvKeys: studioEnvKeys{
|
EnvKeys: studioEnvKeys{
|
||||||
PGMetaURL: "STUDIO_PG_META_URL",
|
PGMetaURL: "STUDIO_PG_META_URL",
|
||||||
DBPassword: "POSTGRES_PASSWORD",
|
DBPassword: "POSTGRES_PASSWORD",
|
||||||
|
@ -47,7 +47,7 @@ func studioServiceConfig() serviceConfig[studioEnvKeys, studioDefaults] {
|
||||||
AnonKey: "SUPABASE_ANON_KEY",
|
AnonKey: "SUPABASE_ANON_KEY",
|
||||||
ServiceKey: "SUPABASE_SERVICE_KEY",
|
ServiceKey: "SUPABASE_SERVICE_KEY",
|
||||||
Host: fixedEnvOf("HOSTNAME", "0.0.0.0"),
|
Host: fixedEnvOf("HOSTNAME", "0.0.0.0"),
|
||||||
LogsEnabled: fixedEnvOf("NEXT_PUBLIC_ENABLE_LOGS", "true"),
|
LogsEnabled: fixedEnvOf("NEXT_PUBLIC_ENABLE_LOGS", "false"),
|
||||||
},
|
},
|
||||||
Defaults: studioDefaults{
|
Defaults: studioDefaults{
|
||||||
NodeUID: 1000,
|
NodeUID: 1000,
|
||||||
|
|
|
@ -44,6 +44,7 @@ const (
|
||||||
|
|
||||||
var ignoredMigrations = []string{
|
var ignoredMigrations = []string{
|
||||||
"10000000000000_demote-postgres.sql",
|
"10000000000000_demote-postgres.sql",
|
||||||
|
"20250312095419_pgbouncer_ownership.sql",
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateAll(ctx context.Context) {
|
func GenerateAll(ctx context.Context) {
|
||||||
|
@ -147,7 +148,7 @@ func FetchImageMeta(ctx context.Context) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
latestEnvoyTag, err := latestReleaseVersion(ctx, "envoyproxy", "envoy")
|
latestEnvoyTag, err := latestReleaseVersion(ctx, "envoyproxy", "envoy", excludeDrafts, excludePreReleases)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -169,11 +170,13 @@ func FetchImageMeta(ctx context.Context) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchMigrations(ctx context.Context) (err error) {
|
func FetchMigrations(ctx context.Context) (err error) {
|
||||||
latestRelease, err := latestReleaseVersion(ctx, "supabase", "postgres")
|
latestRelease, err := latestReleaseVersion(ctx, "supabase", "postgres", excludeDrafts, excludePreReleases, matchesTagPattern(`15\..*`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slog.InfoContext(ctx, "Extracting Postgres migrations for release", slog.String("release", latestRelease))
|
||||||
|
|
||||||
releaseArtifactURL := fmt.Sprintf("https://github.com/supabase/postgres/archive/refs/tags/%s.tar.gz", latestRelease)
|
releaseArtifactURL := fmt.Sprintf("https://github.com/supabase/postgres/archive/refs/tags/%s.tar.gz", latestRelease)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, releaseArtifactURL, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, releaseArtifactURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -21,12 +21,42 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"code.icb4dc0.de/prskr/supabase-operator/internal/errx"
|
"code.icb4dc0.de/prskr/supabase-operator/internal/errx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func latestReleaseVersion(ctx context.Context, owner, repo string) (tagName string, err error) {
|
type releaseMatcher interface {
|
||||||
releaseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
|
matchesRelease(r release) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
excludeDrafts releaseMatcher = releaseMatcherFunc(func(r release) bool {
|
||||||
|
return !r.Draft
|
||||||
|
})
|
||||||
|
excludePreReleases releaseMatcher = releaseMatcherFunc(func(r release) bool {
|
||||||
|
return !r.PreRelease
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
func matchesTagPattern(pattern string) releaseMatcher {
|
||||||
|
compiled, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return releaseMatcherFunc(func(r release) bool {
|
||||||
|
return compiled.MatchString(r.TagName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type release struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
Draft bool `json:"draft"`
|
||||||
|
PreRelease bool `json:"prerelease"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func latestReleaseVersion(ctx context.Context, owner, repo string, matchers ...releaseMatcher) (tagName string, err error) {
|
||||||
|
releaseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases", owner, repo)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, releaseURL, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, releaseURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -43,13 +73,37 @@ func latestReleaseVersion(ctx context.Context, owner, repo string) (tagName stri
|
||||||
return "", fmt.Errorf("failed to retrieve latest release: %s", resp.Status)
|
return "", fmt.Errorf("failed to retrieve latest release: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
var release struct {
|
var (
|
||||||
TagName string `json:"tag_name"`
|
releases []release
|
||||||
}
|
matcher = multiMatcher(matchers)
|
||||||
|
)
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return release.TagName, nil
|
for _, release := range releases {
|
||||||
|
if matcher.matchesRelease(release) {
|
||||||
|
return release.TagName, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("no release found matching the criteria: %s/%s", owner, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiMatcher []releaseMatcher
|
||||||
|
|
||||||
|
func (m multiMatcher) matchesRelease(r release) bool {
|
||||||
|
for _, matcher := range m {
|
||||||
|
if !matcher.matchesRelease(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type releaseMatcherFunc func(r release) bool
|
||||||
|
|
||||||
|
func (f releaseMatcherFunc) matchesRelease(r release) bool {
|
||||||
|
return f(r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ public class TaskList : BaseModel
|
||||||
[PrimaryKey("id")]
|
[PrimaryKey("id")]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
[Column("user_id")]
|
[Column("user_id")]
|
||||||
public int UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
[Column("name")]
|
[Column("name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue