feat(ci): configure image build caching
This commit is contained in:
parent
7f56a3db56
commit
264b30e8a2
6 changed files with 151 additions and 34 deletions
.github/workflows
api/v1alpha1
assets/migrations
config/crd/bases
internal/db
4
.github/workflows/postgres.yml
vendored
4
.github/workflows/postgres.yml
vendored
|
@ -1,6 +1,8 @@
|
|||
name: Postgres image
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- postgres/**
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
|
@ -39,6 +41,8 @@ jobs:
|
|||
build-args: |
|
||||
POSTGRES_MAJOR=${{ matrix.postgres_major }}
|
||||
POSTGRES_MINOR=${{ fromJSON(env.MINOR_VERSIONS)[matrix.postgres_major] }}
|
||||
cache-from: type=registry,ref=code.icb4dc0.de/prskr/supabase-operator/postgres:buildcache
|
||||
cache-to: type=registry,ref=code.icb4dc0.de/prskr/supabase-operator/postgres:buildcache,mode=max
|
||||
|
||||
manifest:
|
||||
strategy:
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -25,6 +26,7 @@ import (
|
|||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -387,20 +389,75 @@ type CoreSpec struct {
|
|||
Auth *AuthSpec `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
type MigrationStatus map[string]metav1.Time
|
||||
type MigrationConditionStatus string
|
||||
|
||||
func (s MigrationStatus) IsApplied(name string) bool {
|
||||
_, ok := s[name]
|
||||
return ok
|
||||
}
|
||||
const (
|
||||
MigrationConditionStatusApplied MigrationConditionStatus = "Applied"
|
||||
MigrationConditionStatusFailed MigrationConditionStatus = "Failed"
|
||||
)
|
||||
|
||||
func (s MigrationStatus) Record(name string) {
|
||||
s[name] = metav1.Now()
|
||||
type MigrationScriptCondition struct {
|
||||
// Name - file name of the migration script
|
||||
Name string `json:"name"`
|
||||
// Hash - SHA256 hash of the script when it was last successfully applied
|
||||
Hash []byte `json:"hash"`
|
||||
// Status - whether the migration was applied or not
|
||||
// +kubebuilder:validation:Enum=Applied;Failed
|
||||
Status MigrationConditionStatus `json:"status"`
|
||||
// LastProbeTime - last time the operator tried to execute the migration script
|
||||
LastProbeTime metav1.Time `json:"lastProbeTime,omitempty"`
|
||||
// LastTransitionTime - last time the condition transitioned from one status to another
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
|
||||
// Reason - one-word, CamcelCase reason for the condition's last transition
|
||||
Reason string `json:"reason,omitempty"`
|
||||
// Message - human-readable message indicating details about the last transition
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type DatabaseStatus struct {
|
||||
AppliedMigrations MigrationStatus `json:"appliedMigrations,omitempty"`
|
||||
Roles map[string][]byte `json:"roles,omitempty"`
|
||||
MigrationConditions []MigrationScriptCondition `json:"migrationConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
|
||||
Roles map[string][]byte `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func (s DatabaseStatus) IsMigrationUpToDate(name string, hash []byte) (found bool, upToDate bool) {
|
||||
for _, cond := range s.MigrationConditions {
|
||||
if cond.Name == name {
|
||||
return true, bytes.Equal(cond.Hash, hash)
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
||||
|
||||
func (s DatabaseStatus) RecordMigrationCondition(name string, hash []byte, err error) {
|
||||
var (
|
||||
now = time.Now()
|
||||
newStatus = MigrationConditionStatusApplied
|
||||
lastProbeTime = metav1.NewTime(now)
|
||||
lastTransitionTime metav1.Time
|
||||
message string
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
newStatus = MigrationConditionStatusFailed
|
||||
message = err.Error()
|
||||
}
|
||||
|
||||
for idx, cond := range s.MigrationConditions {
|
||||
if cond.Name == name {
|
||||
lastTransitionTime = cond.LastTransitionTime
|
||||
if cond.Status != newStatus {
|
||||
lastTransitionTime = metav1.NewTime(now)
|
||||
}
|
||||
|
||||
cond.Hash = hash
|
||||
cond.Status = newStatus
|
||||
cond.LastProbeTime = lastProbeTime
|
||||
cond.LastTransitionTime = lastTransitionTime
|
||||
cond.Reason = "Outdated"
|
||||
cond.Message = message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CoreConditionType string
|
||||
|
|
|
@ -752,11 +752,11 @@ func (in *DatabaseRolesSecrets) DeepCopy() *DatabaseRolesSecrets {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DatabaseStatus) DeepCopyInto(out *DatabaseStatus) {
|
||||
*out = *in
|
||||
if in.AppliedMigrations != nil {
|
||||
in, out := &in.AppliedMigrations, &out.AppliedMigrations
|
||||
*out = make(MigrationStatus, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
if in.MigrationConditions != nil {
|
||||
in, out := &in.MigrationConditions, &out.MigrationConditions
|
||||
*out = make([]MigrationScriptCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Roles != nil {
|
||||
|
@ -1046,24 +1046,25 @@ func (in *JwtSpec) DeepCopy() *JwtSpec {
|
|||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in MigrationStatus) DeepCopyInto(out *MigrationStatus) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(MigrationStatus, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
func (in *MigrationScriptCondition) DeepCopyInto(out *MigrationScriptCondition) {
|
||||
*out = *in
|
||||
if in.Hash != nil {
|
||||
in, out := &in.Hash, &out.Hash
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.LastProbeTime.DeepCopyInto(&out.LastProbeTime)
|
||||
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MigrationStatus.
|
||||
func (in MigrationStatus) DeepCopy() MigrationStatus {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MigrationScriptCondition.
|
||||
func (in *MigrationScriptCondition) DeepCopy() *MigrationScriptCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MigrationStatus)
|
||||
out := new(MigrationScriptCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
@ -32,6 +33,7 @@ var migrationsFS embed.FS
|
|||
type Script struct {
|
||||
FileName string
|
||||
Content string
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
func InitScripts() iter.Seq2[Script, error] {
|
||||
|
@ -49,10 +51,18 @@ func RoleCreationScript(roleName string) (Script, error) {
|
|||
return Script{}, err
|
||||
}
|
||||
|
||||
return Script{fileName, string(content)}, nil
|
||||
hash := sha256.New()
|
||||
_, _ = hash.Write(content)
|
||||
|
||||
return Script{
|
||||
FileName: fileName,
|
||||
Content: string(content),
|
||||
Hash: hash.Sum(nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readScripts(dir string) iter.Seq2[Script, error] {
|
||||
hash := sha256.New()
|
||||
return func(yield func(Script, error) bool) {
|
||||
files, err := migrationsFS.ReadDir(dir)
|
||||
if err != nil {
|
||||
|
@ -76,11 +86,16 @@ func readScripts(dir string) iter.Seq2[Script, error] {
|
|||
}
|
||||
}
|
||||
|
||||
_, _ = hash.Write(content)
|
||||
|
||||
s := Script{
|
||||
FileName: file.Name(),
|
||||
Content: string(content),
|
||||
Hash: hash.Sum(nil),
|
||||
}
|
||||
|
||||
hash.Reset()
|
||||
|
||||
if !yield(s, nil) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5353,11 +5353,48 @@ spec:
|
|||
properties:
|
||||
database:
|
||||
properties:
|
||||
appliedMigrations:
|
||||
additionalProperties:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
migrationConditions:
|
||||
items:
|
||||
properties:
|
||||
hash:
|
||||
description: Hash - SHA256 hash of the script when it was
|
||||
last successfully applied
|
||||
format: byte
|
||||
type: string
|
||||
lastProbeTime:
|
||||
description: LastProbeTime - last time the operator tried
|
||||
to execute the migration script
|
||||
format: date-time
|
||||
type: string
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime - last time the condition
|
||||
transitioned from one status to another
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: Message - human-readable message indicating
|
||||
details about the last transition
|
||||
type: string
|
||||
name:
|
||||
description: Name - file name of the migration script
|
||||
type: string
|
||||
reason:
|
||||
description: Reason - one-word, CamcelCase reason for the
|
||||
condition's last transition
|
||||
type: string
|
||||
status:
|
||||
description: Status - whether the migration was applied
|
||||
or not
|
||||
enum:
|
||||
- Applied
|
||||
- Failed
|
||||
type: string
|
||||
required:
|
||||
- hash
|
||||
- name
|
||||
- status
|
||||
type: object
|
||||
type: array
|
||||
roles:
|
||||
additionalProperties:
|
||||
format: byte
|
||||
|
|
|
@ -32,7 +32,7 @@ type Migrator struct {
|
|||
Conn *pgx.Conn
|
||||
}
|
||||
|
||||
func (m Migrator) ApplyAll(ctx context.Context, status supabasev1alpha1.MigrationStatus, seq iter.Seq2[migrations.Script, error]) (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)
|
||||
|
||||
for s, err := range seq {
|
||||
|
@ -40,11 +40,14 @@ func (m Migrator) ApplyAll(ctx context.Context, status supabasev1alpha1.Migratio
|
|||
return false, err
|
||||
}
|
||||
|
||||
if status.IsApplied(s.FileName) {
|
||||
if found, upToDate := status.Database.IsMigrationUpToDate(s.FileName, s.Hash); found && upToDate {
|
||||
continue
|
||||
} else if found && !upToDate && areInitScripts {
|
||||
logger.Info("Change in init script was detected - will not apply because init scripts are not idempotent", "file_name", s.FileName)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("Applying missing migration", "filename", s.FileName)
|
||||
logger.Info("Applying missing or outdated migration", "filename", s.FileName)
|
||||
if err := m.Apply(ctx, s.Content); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue