fix(db): track state of migrations and execute them again when necessary
This commit is contained in:
parent
264b30e8a2
commit
2d71a4a132
15 changed files with 59 additions and 33 deletions
.github/workflows
DockerfileMakefileTiltfileapi/v1alpha1
assets/migrations/setup
config/dev
dev
docs/api
internal
test/e2e
4
.github/workflows/postgres.yml
vendored
4
.github/workflows/postgres.yml
vendored
|
@ -63,6 +63,8 @@ jobs:
|
||||||
|
|
||||||
- name: Create manifest
|
- name: Create manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create -t code.icb4dc0.de/prskr/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }} \
|
docker buildx imagetools create \
|
||||||
|
-t code.icb4dc0.de/prskr/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }} \
|
||||||
|
-t code.icb4dc0.de/prskr/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }} \
|
||||||
code.icb4dc0.de/prskr/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-arm64 \
|
code.icb4dc0.de/prskr/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-arm64 \
|
||||||
code.icb4dc0.de/prskr/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-amd64
|
code.icb4dc0.de/prskr/supabase-operator/postgres:${{ matrix.postgres_major }}.${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}.${{ github.run_number }}-amd64
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Build the manager binary
|
# Build the manager binary
|
||||||
FROM golang:1.23.4 AS builder
|
FROM golang:1.23.6-alpine AS builder
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
@ -16,10 +16,7 @@ COPY [ "go.*", "./" ]
|
||||||
COPY [ "api", "api" ]
|
COPY [ "api", "api" ]
|
||||||
COPY [ "assets/migrations", "assets/migrations" ]
|
COPY [ "assets/migrations", "assets/migrations" ]
|
||||||
COPY [ "cmd", "cmd" ]
|
COPY [ "cmd", "cmd" ]
|
||||||
COPY [ "infrastructure", "infrastructure" ]
|
|
||||||
COPY [ "internal", "internal" ]
|
COPY [ "internal", "internal" ]
|
||||||
COPY [ "magefiles", "magefiles" ]
|
|
||||||
COPY [ "tools", "tools" ]
|
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
# the GOARCH has not a default value to allow the binary be built according to the host where the command
|
# the GOARCH has not a default value to allow the binary be built according to the host where the command
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -148,7 +148,7 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified
|
||||||
|
|
||||||
.PHONY: deploy
|
.PHONY: deploy
|
||||||
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
||||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
cd config/manager && $(KUSTOMIZE) edit set image supabase-operator=${IMG}
|
||||||
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -
|
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -
|
||||||
|
|
||||||
.PHONY: undeploy
|
.PHONY: undeploy
|
||||||
|
|
7
Tiltfile
7
Tiltfile
|
@ -7,13 +7,6 @@ k8s_yaml(kustomize('config/dev'))
|
||||||
|
|
||||||
compile_cmd = 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/supabase-operator ./cmd/'
|
compile_cmd = 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/supabase-operator ./cmd/'
|
||||||
|
|
||||||
update_settings(suppress_unused_image_warnings=["localhost:5005/cnpg-postgres:17.2"])
|
|
||||||
custom_build(
|
|
||||||
'localhost:5005/cnpg-postgres:17.2',
|
|
||||||
'docker build -t $EXPECTED_REF --push -f postgres/Dockerfile --build-arg POSTGRES_MAJOR=17 --build-arg=POSTGRES_MINOR=2 .',
|
|
||||||
['postgres/Dockerfile']
|
|
||||||
)
|
|
||||||
|
|
||||||
local_resource(
|
local_resource(
|
||||||
'manager-go-compile',
|
'manager-go-compile',
|
||||||
compile_cmd,
|
compile_cmd,
|
||||||
|
|
|
@ -429,12 +429,12 @@ func (s DatabaseStatus) IsMigrationUpToDate(name string, hash []byte) (found boo
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s DatabaseStatus) RecordMigrationCondition(name string, hash []byte, err error) {
|
func (s *DatabaseStatus) RecordMigrationCondition(name string, hash []byte, err error) error {
|
||||||
var (
|
var (
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
newStatus = MigrationConditionStatusApplied
|
newStatus = MigrationConditionStatusApplied
|
||||||
lastProbeTime = metav1.NewTime(now)
|
lastProbeTime = metav1.NewTime(now)
|
||||||
lastTransitionTime metav1.Time
|
lastTransitionTime = metav1.NewTime(now)
|
||||||
message string
|
message string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -456,8 +456,22 @@ func (s DatabaseStatus) RecordMigrationCondition(name string, hash []byte, err e
|
||||||
cond.LastTransitionTime = lastTransitionTime
|
cond.LastTransitionTime = lastTransitionTime
|
||||||
cond.Reason = "Outdated"
|
cond.Reason = "Outdated"
|
||||||
cond.Message = message
|
cond.Message = message
|
||||||
|
|
||||||
|
s.MigrationConditions[idx] = cond
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.MigrationConditions = append(s.MigrationConditions, MigrationScriptCondition{
|
||||||
|
Name: name,
|
||||||
|
Hash: hash,
|
||||||
|
Status: newStatus,
|
||||||
|
LastProbeTime: lastProbeTime,
|
||||||
|
LastTransitionTime: lastTransitionTime,
|
||||||
|
Message: message,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type CoreConditionType string
|
type CoreConditionType string
|
||||||
|
|
3
assets/migrations/setup/realtime.sql
Normal file
3
assets/migrations/setup/realtime.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
create schema if not exists _realtime;
|
||||||
|
|
||||||
|
alter schema _realtime owner to supabase_admin;
|
|
@ -44,7 +44,7 @@ metadata:
|
||||||
namespace: supabase-demo
|
namespace: supabase-demo
|
||||||
spec:
|
spec:
|
||||||
instances: 1
|
instances: 1
|
||||||
imageName: localhost:5005/cnpg-postgres:17.2
|
imageName: code.icb4dc0.de/prskr/supabase-operator/postgres:17.2.258
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
postgresUID: 26
|
postgresUID: 26
|
||||||
postgresGID: 102
|
postgresGID: 102
|
||||||
|
|
4
config/dev/kustomizeconfig/cnpg-cluster.yaml
Normal file
4
config/dev/kustomizeconfig/cnpg-cluster.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
images:
|
||||||
|
- kind: Cluster
|
||||||
|
group: postgresql.cnpg.io
|
||||||
|
path: spec/imageName
|
|
@ -1,12 +1,13 @@
|
||||||
apiVersion: ctlptl.dev/v1alpha1
|
apiVersion: ctlptl.dev/v1alpha1
|
||||||
kind: Registry
|
kind: Registry
|
||||||
name: ctlptl-registry
|
name: supabase-operator-registry
|
||||||
port: 5005
|
port: 5005
|
||||||
---
|
---
|
||||||
apiVersion: ctlptl.dev/v1alpha1
|
apiVersion: ctlptl.dev/v1alpha1
|
||||||
kind: Cluster
|
kind: Cluster
|
||||||
product: kind
|
product: kind
|
||||||
registry: ctlptl-registry
|
registry: supabase-operator-registry
|
||||||
kindV1Alpha4Cluster:
|
kindV1Alpha4Cluster:
|
||||||
|
name: supabase-operator-debug
|
||||||
networking:
|
networking:
|
||||||
ipFamily: dual
|
ipFamily: dual
|
||||||
|
|
|
@ -518,7 +518,7 @@ _Appears in:_
|
||||||
|
|
||||||
| Field | Description | Default | Validation |
|
| Field | Description | Default | Validation |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `appliedMigrations` _[MigrationStatus](#migrationstatus)_ | | | |
|
| `migrationConditions` _[MigrationScriptCondition](#migrationscriptcondition) array_ | | | |
|
||||||
| `roles` _object (keys:string, values:integer array)_ | | | |
|
| `roles` _object (keys:string, values:integer array)_ | | | |
|
||||||
|
|
||||||
|
|
||||||
|
@ -773,9 +773,11 @@ _Appears in:_
|
||||||
| `serviceKey` _string_ | ServiceKey - key in secret where to read the service JWT from | service_key | |
|
| `serviceKey` _string_ | ServiceKey - key in secret where to read the service JWT from | service_key | |
|
||||||
|
|
||||||
|
|
||||||
#### MigrationStatus
|
|
||||||
|
|
||||||
_Underlying type:_ _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#time-v1-meta)_
|
|
||||||
|
#### MigrationScriptCondition
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -784,6 +786,14 @@ _Underlying type:_ _[Time](https://kubernetes.io/docs/reference/generated/kubern
|
||||||
_Appears in:_
|
_Appears in:_
|
||||||
- [DatabaseStatus](#databasestatus)
|
- [DatabaseStatus](#databasestatus)
|
||||||
|
|
||||||
|
| Field | Description | Default | Validation |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` _string_ | Name - file name of the migration script | | |
|
||||||
|
| `hash` _integer array_ | Hash - SHA256 hash of the script when it was last successfully applied | | |
|
||||||
|
| `lastProbeTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#time-v1-meta)_ | LastProbeTime - last time the operator tried to execute the migration script | | |
|
||||||
|
| `lastTransitionTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#time-v1-meta)_ | LastTransitionTime - last time the condition transitioned from one status to another | | |
|
||||||
|
| `reason` _string_ | Reason - one-word, CamcelCase reason for the condition's last transition | | |
|
||||||
|
| `message` _string_ | Message - human-readable message indicating details about the last transition | | |
|
||||||
|
|
||||||
|
|
||||||
#### OAuthProvider
|
#### OAuthProvider
|
||||||
|
|
|
@ -105,11 +105,7 @@ func (r *CoreDbReconciler) applyMissingMigrations(
|
||||||
|
|
||||||
var appliedSomething bool
|
var appliedSomething bool
|
||||||
|
|
||||||
if core.Status.Database.AppliedMigrations == nil {
|
if appliedSomething, err = migrator.ApplyAll(ctx, &core.Status, migrations.InitScripts(), true); err != nil {
|
||||||
core.Status.Database.AppliedMigrations = make(supabasev1alpha1.MigrationStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
if appliedSomething, err = migrator.ApplyAll(ctx, core.Status.Database.AppliedMigrations, migrations.InitScripts()); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +116,7 @@ func (r *CoreDbReconciler) applyMissingMigrations(
|
||||||
logger.Info("Init scripts were up to date - did not run any")
|
logger.Info("Init scripts were up to date - did not run any")
|
||||||
}
|
}
|
||||||
|
|
||||||
if appliedSomething, err = migrator.ApplyAll(ctx, core.Status.Database.AppliedMigrations, migrations.MigrationScripts()); err != nil {
|
if appliedSomething, err = migrator.ApplyAll(ctx, &core.Status, migrations.MigrationScripts(), false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,12 @@ type Migrator struct {
|
||||||
Conn *pgx.Conn
|
Conn *pgx.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Migrator) ApplyAll(ctx context.Context, status *supabasev1alpha1.CoreStatus, seq iter.Seq2[migrations.Script, error], areInitScripts bool) (appliedSomething bool, err error) {
|
func (m Migrator) ApplyAll(
|
||||||
|
ctx context.Context,
|
||||||
|
status *supabasev1alpha1.CoreStatus,
|
||||||
|
seq iter.Seq2[migrations.Script, error],
|
||||||
|
areInitScripts bool,
|
||||||
|
) (appliedSomething bool, err error) {
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
for s, err := range seq {
|
for s, err := range seq {
|
||||||
|
@ -48,12 +53,12 @@ func (m Migrator) ApplyAll(ctx context.Context, status *supabasev1alpha1.CoreSta
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Applying missing or outdated migration", "filename", s.FileName)
|
logger.Info("Applying missing or outdated migration", "filename", s.FileName)
|
||||||
if err := m.Apply(ctx, s.Content); err != nil {
|
err := status.Database.RecordMigrationCondition(s.FileName, s.Hash, m.Apply(ctx, s.Content))
|
||||||
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
appliedSomething = true
|
appliedSomething = true
|
||||||
status.Record(s.FileName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return appliedSomething, nil
|
return appliedSomething, nil
|
||||||
|
|
|
@ -106,6 +106,7 @@ func (v *APIGatewayCustomValidator) ValidateDelete(ctx context.Context, obj runt
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:unparam // keep the warnings for future use cases
|
||||||
func validateEnvoyControlPlane(gateway *supabasev1alpha1.APIGateway) (admission.Warnings, error) {
|
func validateEnvoyControlPlane(gateway *supabasev1alpha1.APIGateway) (admission.Warnings, error) {
|
||||||
envoySpec := gateway.Spec.Envoy
|
envoySpec := gateway.Spec.Envoy
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ var (
|
||||||
|
|
||||||
// projectImage is the name of the image which will be build and loaded
|
// projectImage is the name of the image which will be build and loaded
|
||||||
// with the code source changes to be tested.
|
// with the code source changes to be tested.
|
||||||
projectImage = "example.com/supabase-operator:v0.0.1"
|
projectImage = "code.icb4dc0.de/prskr/supabase-operator:v0.0.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
|
// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
|
||||||
|
|
|
@ -31,13 +31,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// namespace where the project is deployed in
|
// namespace where the project is deployed in
|
||||||
const namespace = "supabase-operator-system"
|
const namespace = "supabase-system"
|
||||||
|
|
||||||
// serviceAccountName created for the project
|
// serviceAccountName created for the project
|
||||||
const serviceAccountName = "supabase-operator-controller-manager"
|
const serviceAccountName = "supabase-operator-controller-manager"
|
||||||
|
|
||||||
// metricsServiceName is the name of the metrics service of the project
|
// metricsServiceName is the name of the metrics service of the project
|
||||||
const metricsServiceName = "supabase-operator-controller-manager-metrics-service"
|
const metricsServiceName = "supabase-controller-manager-metrics-service"
|
||||||
|
|
||||||
// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data
|
// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data
|
||||||
const metricsRoleBindingName = "supabase-operator-metrics-binding"
|
const metricsRoleBindingName = "supabase-operator-metrics-binding"
|
||||||
|
|
Loading…
Add table
Reference in a new issue