snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-16 01:08:34 +05:00
commit 9456a4e922
2560 changed files with 120911 additions and 0 deletions

86
cmd/migrate/down.go Normal file
View File

@@ -0,0 +1,86 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package migrate
import (
"fmt"
"strconv"
"github.com/ory/x/cmdx"
"github.com/ory/x/flagx"
"github.com/ory/x/popx"
"github.com/spf13/cobra"
"github.com/ory/keto/internal/driver"
"github.com/ory/keto/ketoctx"
)
func newDownCmd(opts []ketoctx.Option) *cobra.Command {
cmd := &cobra.Command{
Use: "down <steps>",
Short: "Migrate the database down",
Long: "Migrate the database down a specific amount of steps.\n" +
"Pass 0 steps to fully migrate down.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
steps, err := strconv.ParseInt(args[0], 0, 0)
if err != nil {
// return this error so it gets printed along the usage
return fmt.Errorf("malformed argument %s for <steps>: %+v", args[0], err)
}
reg, err := driver.NewDefaultRegistry(cmd.Context(), cmd.Flags(), true, opts)
if err != nil {
return err
}
conn, err := reg.PopConnection(cmd.Context())
if err != nil {
return err
}
if err := popx.VerifyDialect(cmd.Context(), conn); err != nil {
return err
}
mb, err := reg.MigrationBox(cmd.Context(), popx.WithoutGoldenDatabase())
if err != nil {
return err
}
return BoxDown(cmd, mb, int(steps))
},
}
RegisterYesFlag(cmd.Flags())
cmdx.RegisterFormatFlags(cmd.Flags())
return cmd
}
func BoxDown(cmd *cobra.Command, mb *popx.MigrationBox, steps int) error {
s, err := mb.Status(cmd.Context())
if err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get migration status: %+v\n", err)
return cmdx.FailSilently(cmd)
}
cmdx.PrintTable(cmd, s)
if !flagx.MustGetBool(cmd, FlagYes) && !cmdx.AskForConfirmation("Do you really want to migrate down? This will delete data.", cmd.InOrStdin(), cmd.OutOrStdout()) {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Migration aborted.")
return nil
}
if err := mb.Down(cmd.Context(), steps); err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could apply down migrations: %+v\n", err)
return cmdx.FailSilently(cmd)
}
s, err = mb.Status(cmd.Context())
if err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get migration status: %+v\n", err)
return cmdx.FailSilently(cmd)
}
cmdx.PrintTable(cmd, s)
return nil
}

181
cmd/migrate/migrate_test.go Normal file
View File

@@ -0,0 +1,181 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package migrate
import (
"bytes"
"context"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ory/x/cmdx"
"github.com/ory/x/configx"
"github.com/ory/x/dbal"
"github.com/ory/keto/internal/driver/config"
"github.com/ory/keto/internal/namespace"
"github.com/ory/keto/internal/x/dbx"
)
func assertAllApplied(t *testing.T, status string) {
assert.NotContains(t, status, "Pending")
assert.Contains(t, status, "Applied")
}
func assertNoneApplied(t *testing.T, status string) {
assert.Contains(t, status, "Pending")
assert.NotContains(t, status, "Applied")
}
func TestMigrate(t *testing.T) {
t.Parallel()
nspaces := []*namespace.Namespace{
{
Name: "default",
},
{
Name: "other",
},
}
newCmd := func(ctx context.Context, persistentArgs ...string) *cmdx.CommandExecuter {
return &cmdx.CommandExecuter{
New: func() *cobra.Command {
cmd := NewMigrateCmd(nil)
configx.RegisterFlags(cmd.PersistentFlags())
return cmd
},
Ctx: ctx,
PersistentArgs: persistentArgs,
}
}
for _, dsn := range dbx.GetDSNs(t) {
if dbal.IsMemorySQLite(dsn.Conn) {
t.Run("dsn=memory", func(t *testing.T) {
t.Parallel()
t.Run("case=auto migrates", func(t *testing.T) {
cf := dbx.ConfigFile(t, map[string]interface{}{
config.KeyDSN: dsn.Conn,
config.KeyNamespaces: nspaces,
"log.level": "debug",
})
cmd := newCmd(t.Context(), "-c", cf)
out := cmd.ExecNoErr(t, "up", "--"+FlagYes)
assert.Contains(t, out, "All migrations are already applied, there is nothing to do.")
})
})
} else {
t.Run("dsn="+dsn.Name, func(t *testing.T) {
t.Parallel()
cf := dbx.ConfigFile(t, map[string]interface{}{
config.KeyDSN: dsn.Conn,
config.KeyNamespaces: nspaces,
"log.level": "debug",
})
cmd := newCmd(t.Context(), "-c", cf)
t.Run("case=shows status", func(t *testing.T) {
stdOut := cmd.ExecNoErr(t, "status")
assert.Contains(t, stdOut, "Pending")
assert.NotContains(t, stdOut, "Applied")
})
t.Run("case=status blocks until all are applied", func(t *testing.T) {
cmd := *cmd
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
cmd.Ctx = ctx
stdErr := &bytes.Buffer{}
stdOut := &cmdx.CallbackWriter{
Callbacks: map[string]func([]byte) error{
"Waiting for migrations to finish...": func([]byte) error {
cancel()
return nil
},
},
}
err := cmd.ExecBackground(nil, stdOut, stdErr, "status", "--block").Wait()
require.ErrorIs(t, err, cmdx.ErrNoPrintButFail)
assert.Contains(t, stdOut.String(), "Waiting for migrations to finish...")
assert.Contains(t, stdErr.String(), "Context was canceled, exiting...", stdOut.String())
})
t.Run("case=aborts on no", func(t *testing.T) {
stdOut, stdErr, err := cmd.Exec(strings.NewReader("n\n"), "up")
require.NoError(t, err, "%s %s", stdOut, stdErr)
assert.Containsf(t, stdOut, "Pending", "%s %s", stdOut, stdErr)
assert.NotContainsf(t, stdOut, "Applied", "%s %s", stdOut, stdErr)
assert.Containsf(t, stdOut, "Aborting", "%s %s", stdOut, stdErr)
})
t.Run("case=applies on yes input", func(t *testing.T) {
stdOut, stdErr, err := cmd.Exec(bytes.NewBufferString("y\n"), "up")
require.NoErrorf(t, err, "%s %s", stdOut, stdErr)
t.Cleanup(func() {
// migrate all down
t.Logf("cleanup:\n%s\n", cmd.ExecNoErr(t, "down", "0", "--"+FlagYes))
})
parts := strings.Split(stdOut, "Are you sure that you want to apply this migration?")
require.Len(t, parts, 2)
assertNoneApplied(t, parts[0])
assertAllApplied(t, parts[1])
})
t.Run("case=applies on yes flag", func(t *testing.T) {
out := cmd.ExecNoErr(t, "up", "--"+FlagYes)
t.Cleanup(func() {
// migrate all down
t.Logf("cleanup:\n%s\n", cmd.ExecNoErr(t, "down", "0", "--"+FlagYes))
})
parts := strings.Split(out, "Applying migrations...")
require.Len(t, parts, 2)
assertNoneApplied(t, parts[0])
assertAllApplied(t, parts[1])
})
})
}
}
}
func TestUpAndDown(t *testing.T) {
t.Parallel()
newCmd := func() *cobra.Command {
cmd := NewMigrateCmd(nil)
configx.RegisterFlags(cmd.PersistentFlags())
return cmd
}
for _, dsn := range dbx.GetDSNs(t) {
t.Run("dsn="+dsn.Name, func(t *testing.T) {
cf := dbx.ConfigFile(t, map[string]interface{}{
config.KeyDSN: dsn.Conn,
config.KeyNamespaces: []*namespace.Namespace{},
})
t.Log(cmdx.ExecNoErr(t, newCmd(), "up", "-c", cf, "--"+FlagYes))
t.Log(cmdx.ExecNoErr(t, newCmd(), "down", "0", "-c", cf, "--"+FlagYes))
})
}
}

29
cmd/migrate/root.go Normal file
View File

@@ -0,0 +1,29 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package migrate
import (
"github.com/spf13/cobra"
"github.com/ory/keto/ketoctx"
)
func NewMigrateCmd(opts []ketoctx.Option) *cobra.Command {
cmd := &cobra.Command{
Use: "migrate",
Short: "Commands to migrate the database",
Long: "Commands to migrate the database.\n" +
"This does not affect namespaces. Use `keto namespace migrate` for migrating namespaces.",
}
cmd.AddCommand(
newStatusCmd(opts),
newUpCmd(opts),
newDownCmd(opts),
)
return cmd
}
func RegisterCommandsRecursive(parent *cobra.Command, opts []ketoctx.Option) {
parent.AddCommand(NewMigrateCmd(opts))
}

82
cmd/migrate/status.go Normal file
View File

@@ -0,0 +1,82 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package migrate
import (
"fmt"
"time"
"github.com/ory/x/popx"
"github.com/ory/x/cmdx"
"github.com/spf13/cobra"
"github.com/ory/keto/internal/driver"
"github.com/ory/keto/ketoctx"
)
func newStatusCmd(opts []ketoctx.Option) *cobra.Command {
block := false
cmd := &cobra.Command{
Use: "status",
Short: "Get the current migration status",
Long: "Get the current migration status.\n" +
"This does not affect namespaces. Use `keto namespace migrate status` for migrating namespaces.",
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
reg, err := driver.NewDefaultRegistry(ctx, cmd.Flags(), true, opts)
if err != nil {
return err
}
conn, err := reg.PopConnection(ctx)
if err != nil {
return err
}
if err := popx.VerifyDialect(ctx, conn); err != nil {
return err
}
mb, err := reg.MigrationBox(ctx)
if err != nil {
return err
}
s, err := mb.Status(ctx)
if err != nil {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Could not get migration status: %+v\n", err)
return cmdx.FailSilently(cmd)
}
for block && s.HasPending() {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Waiting for migrations to finish...\n")
for _, m := range s {
if m.State == popx.Pending {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " - %s\n", m.Name)
}
}
select {
case <-ctx.Done():
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Context was canceled, exiting...")
return cmdx.FailSilently(cmd)
case <-time.After(time.Second):
}
s, err = mb.Status(ctx)
if err != nil {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Could not get migration status: %+v\n", err)
return cmdx.FailSilently(cmd)
}
}
cmdx.PrintTable(cmd, s)
return nil
},
}
cmdx.RegisterFormatFlags(cmd.Flags())
cmd.Flags().BoolVar(&block, "block", false, "Block until all migrations have been applied")
return cmd
}

114
cmd/migrate/up.go Normal file
View File

@@ -0,0 +1,114 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package migrate
import (
"fmt"
"github.com/ory/x/cmdx"
"github.com/ory/x/flagx"
"github.com/ory/x/popx"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/ory/keto/internal/driver"
"github.com/ory/keto/ketoctx"
)
const (
FlagYes = "yes"
)
func newUpCmd(opts []ketoctx.Option) *cobra.Command {
cmd := &cobra.Command{
Use: "up",
Short: "Migrate the database up",
Long: `Run this command on a fresh SQL installation and when you upgrade Ory Keto from version v0.7.x and later.
It is recommended to run this command close to the SQL instance (e.g. same subnet) instead of over the public internet.
This decreases risk of failure and decreases time required.
### WARNING ###
Before running this command on an existing database, create a back up!
`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
reg, err := driver.NewDefaultRegistry(ctx, cmd.Flags(), true, opts)
if err != nil {
return err
}
conn, err := reg.PopConnection(ctx)
if err != nil {
return err
}
if err := popx.VerifyDialect(ctx, conn); err != nil {
return err
}
mb, err := reg.MigrationBox(ctx, popx.WithoutGoldenDatabase())
if err != nil {
return err
}
if err := BoxUp(cmd, mb); err != nil {
return err
}
return nil
},
}
RegisterYesFlag(cmd.Flags())
cmdx.RegisterFormatFlags(cmd.Flags())
return cmd
}
func RegisterYesFlag(flags *pflag.FlagSet) {
flags.BoolP(FlagYes, "y", false, "yes to all questions, no user input required")
}
func BoxUp(cmd *cobra.Command, mb *popx.MigrationBox) error {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Current status:")
s, err := mb.Status(cmd.Context())
if err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get migration status: %+v\n", err)
return cmdx.FailSilently(cmd)
}
cmdx.PrintTable(cmd, s)
if !s.HasPending() {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "All migrations are already applied, there is nothing to do.")
return nil
}
if !flagx.MustGetBool(cmd, FlagYes) && !cmdx.AskForConfirmation("Are you sure that you want to apply this migration? Make sure to check the CHANGELOG.md for breaking changes beforehand.", cmd.InOrStdin(), cmd.OutOrStdout()) {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Aborting")
return nil
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Applying migrations...")
if err := mb.Up(cmd.Context()); err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not apply migrations: %+v\n", err)
return cmdx.FailSilently(cmd)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Successfully applied all migrations:")
s, err = mb.Status(cmd.Context())
if err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get migration status: %+v\n", err)
return cmdx.FailSilently(cmd)
}
cmdx.PrintTable(cmd, s)
return nil
}