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*"
|
||||
|
||||
env:
|
||||
MINOR_VERSIONS: '{"15": "12","17": "4"}'
|
||||
MINOR_VERSIONS: '{"15":"12","17":"4"}'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -22,9 +22,9 @@ jobs:
|
|||
- name: Login to container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: code.icb4dc0.de
|
||||
username: prskr
|
||||
password: ${{ secrets.RELEASE_TOKEN }}
|
||||
registry: registry.icb4dc0.de
|
||||
username: ${{ secrets.HARBOR_USER }}
|
||||
password: ${{ secrets.HARBOR_TOKEN }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
|
|
|
@ -8,7 +8,7 @@ before:
|
|||
- go mod tidy
|
||||
- go run mage.go GenerateAll
|
||||
- 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"
|
||||
|
||||
builds:
|
||||
|
@ -28,7 +28,7 @@ kos:
|
|||
base_image: gcr.io/distroless/static:nonroot
|
||||
user: "65532:65532"
|
||||
repositories:
|
||||
- code.icb4dc0.de/prskr/supabase-operator
|
||||
- registry.icb4dc0.de/supabase-operator/controller
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
# git hook pre commit
|
||||
pre-commit = [
|
||||
"go mod tidy -go=1.24",
|
||||
"go run mage.go GenerateAll",
|
||||
"husky lint-staged",
|
||||
# "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')
|
||||
|
||||
docker_build_with_restart(
|
||||
'supabase-operator',
|
||||
'controller',
|
||||
'.',
|
||||
entrypoint=['/app/bin/supabase-operator'],
|
||||
dockerfile='dev/Dockerfile',
|
||||
|
|
|
@ -18,7 +18,15 @@ grant pg_read_all_data to supabase_read_only_user;
|
|||
create schema if not exists 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 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
|
||||
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:
|
||||
- args:
|
||||
- control-plane
|
||||
image: supabase-operator:latest
|
||||
image: controller:latest
|
||||
name: control-plane
|
||||
env:
|
||||
- name: CONTROL_PLANE_NAMESPACE
|
||||
|
|
|
@ -44,7 +44,7 @@ metadata:
|
|||
namespace: supabase-demo
|
||||
spec:
|
||||
instances: 1
|
||||
imageName: code.icb4dc0.de/prskr/supabase-operator/postgres:17.2
|
||||
imageName: registry.icb4dc0.de/supabase-operator/postgres:17.4
|
||||
imagePullPolicy: Always
|
||||
postgresUID: 26
|
||||
postgresGID: 102
|
||||
|
|
|
@ -29,7 +29,7 @@ spec:
|
|||
- manager
|
||||
- --leader-elect
|
||||
- --health-probe-bind-address=:8081
|
||||
image: supabase-operator:latest
|
||||
image: controller:latest
|
||||
name: manager
|
||||
env:
|
||||
- name: CONTROLLER_NAMESPACE
|
||||
|
|
|
@ -2,9 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||
kind: Kustomization
|
||||
|
||||
images:
|
||||
- name: supabase-operator
|
||||
newName: code.icb4dc0.de/prskr/supabase-operator
|
||||
newTag: 0.1.0-SNAPSHOT-e6c7d68
|
||||
- name: controller
|
||||
newName: registry.icb4dc0.de/supabase-operator/controller
|
||||
newTag: 0.1.0-SNAPSHOT-e6c7d68
|
||||
|
||||
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)
|
||||
err := status.Database.RecordMigrationCondition(s.FileName, s.Hash, m.Apply(ctx, s.Content))
|
||||
if err != nil {
|
||||
return false, err
|
||||
logger.Error(err, "Failed to apply migrations", "filename", s.FileName)
|
||||
continue
|
||||
}
|
||||
|
||||
appliedSomething = true
|
||||
|
|
|
@ -37,7 +37,7 @@ type studioDefaults struct {
|
|||
func studioServiceConfig() serviceConfig[studioEnvKeys, studioDefaults] {
|
||||
return serviceConfig[studioEnvKeys, studioDefaults]{
|
||||
Name: "studio",
|
||||
LivenessProbePath: "/api/profile",
|
||||
LivenessProbePath: "/api/platform/profile",
|
||||
EnvKeys: studioEnvKeys{
|
||||
PGMetaURL: "STUDIO_PG_META_URL",
|
||||
DBPassword: "POSTGRES_PASSWORD",
|
||||
|
@ -47,7 +47,7 @@ func studioServiceConfig() serviceConfig[studioEnvKeys, studioDefaults] {
|
|||
AnonKey: "SUPABASE_ANON_KEY",
|
||||
ServiceKey: "SUPABASE_SERVICE_KEY",
|
||||
Host: fixedEnvOf("HOSTNAME", "0.0.0.0"),
|
||||
LogsEnabled: fixedEnvOf("NEXT_PUBLIC_ENABLE_LOGS", "true"),
|
||||
LogsEnabled: fixedEnvOf("NEXT_PUBLIC_ENABLE_LOGS", "false"),
|
||||
},
|
||||
Defaults: studioDefaults{
|
||||
NodeUID: 1000,
|
||||
|
|
|
@ -44,6 +44,7 @@ const (
|
|||
|
||||
var ignoredMigrations = []string{
|
||||
"10000000000000_demote-postgres.sql",
|
||||
"20250312095419_pgbouncer_ownership.sql",
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -169,11 +170,13 @@ func FetchImageMeta(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 {
|
||||
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)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, releaseArtifactURL, nil)
|
||||
if err != nil {
|
||||
|
|
|
@ -21,12 +21,42 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"code.icb4dc0.de/prskr/supabase-operator/internal/errx"
|
||||
)
|
||||
|
||||
func latestReleaseVersion(ctx context.Context, owner, repo string) (tagName string, err error) {
|
||||
releaseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
|
||||
type releaseMatcher interface {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
var release struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
var (
|
||||
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 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")]
|
||||
public int Id { get; set; }
|
||||
[Column("user_id")]
|
||||
public int UserId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
[Column("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue