snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (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:
105
cmd/migrate/handler.go
Normal file
105
cmd/migrate/handler.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/x/popx"
|
||||
|
||||
"github.com/ory/x/contextx"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/x/cmdx"
|
||||
"github.com/ory/x/flagx"
|
||||
)
|
||||
|
||||
type MigrateHandler struct{}
|
||||
|
||||
func NewMigrateHandler() *MigrateHandler {
|
||||
return &MigrateHandler{}
|
||||
}
|
||||
|
||||
func (h *MigrateHandler) getPersister(cmd *cobra.Command, args []string, opts []driver.RegistryOption) (driver.Registry, error) {
|
||||
return getPersister(cmd, args, opts)
|
||||
}
|
||||
|
||||
func getPersister(cmd *cobra.Command, args []string, opts []driver.RegistryOption) (d driver.Registry, err error) {
|
||||
if flagx.MustGetBool(cmd, "read-from-env") {
|
||||
d, err = driver.NewWithoutInit(
|
||||
cmd.Context(),
|
||||
cmd.ErrOrStderr(),
|
||||
driver.WithConfigOptions(
|
||||
configx.WithFlags(cmd.Flags()),
|
||||
configx.SkipValidation(),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(d.Config().DSN(cmd.Context())) == 0 {
|
||||
fmt.Println(cmd.UsageString())
|
||||
fmt.Println("")
|
||||
fmt.Println("When using flag -e, environment variable DSN must be set")
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
}
|
||||
} else {
|
||||
if len(args) != 1 {
|
||||
fmt.Println(cmd.UsageString())
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
}
|
||||
d, err = driver.NewWithoutInit(
|
||||
cmd.Context(),
|
||||
cmd.ErrOrStderr(),
|
||||
driver.WithConfigOptions(
|
||||
configx.WithFlags(cmd.Flags()),
|
||||
configx.SkipValidation(),
|
||||
configx.WithValue(config.ViperKeyDSN, args[0]),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = d.Init(cmd.Context(), &contextx.Default{}, append(opts, driver.SkipNetworkInit)...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "an error occurred initializing migrations")
|
||||
}
|
||||
|
||||
if err := popx.VerifyDialect(cmd.Context(), d.Persister().GetConnection(cmd.Context())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (h *MigrateHandler) MigrateSQLDown(cmd *cobra.Command, args []string, opts ...driver.RegistryOption) error {
|
||||
p, err := h.getPersister(cmd, args, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return popx.MigrateSQLDown(cmd, p.Persister())
|
||||
}
|
||||
|
||||
func (h *MigrateHandler) MigrateSQLStatus(cmd *cobra.Command, args []string, opts ...driver.RegistryOption) error {
|
||||
p, err := h.getPersister(cmd, args, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return popx.MigrateStatus(cmd, p.Persister())
|
||||
}
|
||||
|
||||
func (h *MigrateHandler) MigrateSQLUp(cmd *cobra.Command, args []string, opts ...driver.RegistryOption) error {
|
||||
p, err := h.getPersister(cmd, args, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return popx.MigrateSQLUp(cmd, p.Persister())
|
||||
}
|
||||
55
cmd/migrate/normalize_phone.go
Normal file
55
cmd/migrate/normalize_phone.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/x/configx"
|
||||
)
|
||||
|
||||
func NewNormalizePhoneCmd(opts ...driver.RegistryOption) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "normalize-phone-numbers [database-url]",
|
||||
Short: "Normalize phone numbers to E.164 format in the database",
|
||||
Long: `Normalizes all phone numbers in identity_credential_identifiers,
|
||||
identity_verifiable_addresses, and identity_recovery_addresses to E.164 format.
|
||||
|
||||
This command uses keyset pagination to iterate over the database in batches.
|
||||
It is safe to run multiple times (idempotent) and can be interrupted and resumed
|
||||
using the --start-after flag with the last ID printed in the progress output.
|
||||
|
||||
Run this command AFTER deploying the code changes that normalize phone numbers
|
||||
on write, to ensure all legacy data is also normalized.
|
||||
|
||||
You can read in the database URL using the -e flag, for example:
|
||||
export DSN=...
|
||||
kratos migrate normalize-phone-numbers -e
|
||||
|
||||
### WARNING ###
|
||||
Before running this command on an existing database, create a back up!
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := NewNormalizePhoneHandler().NormalizePhoneNumbers(cmd, args, opts...)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
configx.RegisterFlags(c.PersistentFlags())
|
||||
c.Flags().BoolP("read-from-env", "e", false, "If set, reads the database connection string from the environment variable DSN or config file key dsn.")
|
||||
c.Flags().IntP("batch-size", "b", 1000, "Number of rows to process per batch")
|
||||
c.Flags().Bool("dry-run", false, "If set, only report what would change without writing")
|
||||
c.Flags().Duration("batch-delay", time.Second, "Delay between batches to reduce database load (e.g. 100ms, 1s)")
|
||||
c.Flags().StringSlice("start-after", nil, "Resume after a table's last processed ID, e.g. --start-after credentials=<id> --start-after verifiable=<id>")
|
||||
|
||||
return c
|
||||
}
|
||||
221
cmd/migrate/normalize_phone_handler.go
Normal file
221
cmd/migrate/normalize_phone_handler.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/pop/v6"
|
||||
"github.com/ory/x/flagx"
|
||||
)
|
||||
|
||||
// phonePattern matches values that look like phone numbers: + followed by at least one digit,
|
||||
// possibly with spaces, dashes, or parens. This filters out OIDC subjects and other
|
||||
// identifiers that happen to start with +.
|
||||
var phonePattern = regexp.MustCompile(`^\+[\d\s\-().]+$`)
|
||||
|
||||
type NormalizePhoneHandler struct{}
|
||||
|
||||
func NewNormalizePhoneHandler() *NormalizePhoneHandler {
|
||||
return &NormalizePhoneHandler{}
|
||||
}
|
||||
|
||||
type normalizeStats struct {
|
||||
scanned int
|
||||
updated int
|
||||
skipped int
|
||||
errors int
|
||||
}
|
||||
|
||||
func (h *NormalizePhoneHandler) NormalizePhoneNumbers(cmd *cobra.Command, args []string, opts ...driver.RegistryOption) error {
|
||||
d, err := getPersister(cmd, args, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn := d.Persister().GetConnection(cmd.Context())
|
||||
batchSize := flagx.MustGetInt(cmd, "batch-size")
|
||||
dryRun := flagx.MustGetBool(cmd, "dry-run")
|
||||
batchDelay := flagx.MustGetDuration(cmd, "batch-delay")
|
||||
|
||||
if dryRun {
|
||||
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Dry run mode enabled. No changes will be written.")
|
||||
}
|
||||
|
||||
tables := []tableConfig{
|
||||
{
|
||||
key: "ci",
|
||||
name: "Credential identifiers",
|
||||
selectQuery: `
|
||||
SELECT id, identifier AS value
|
||||
FROM identity_credential_identifiers
|
||||
WHERE identifier LIKE '+%'
|
||||
AND id > ?
|
||||
ORDER BY id ASC LIMIT ?`,
|
||||
updateQuery: `UPDATE identity_credential_identifiers SET identifier = ?, updated_at = ? WHERE id = ? AND identifier = ?`,
|
||||
},
|
||||
{
|
||||
key: "va",
|
||||
name: "Verifiable addresses",
|
||||
selectQuery: `
|
||||
SELECT id, value
|
||||
FROM identity_verifiable_addresses
|
||||
WHERE via = 'sms'
|
||||
AND id > ?
|
||||
AND value LIKE '+%'
|
||||
ORDER BY id ASC LIMIT ?`,
|
||||
updateQuery: `UPDATE identity_verifiable_addresses SET value = ?, updated_at = ? WHERE id = ? AND value = ?`,
|
||||
},
|
||||
{
|
||||
key: "ra",
|
||||
name: "Recovery addresses",
|
||||
selectQuery: `
|
||||
SELECT id, value
|
||||
FROM identity_recovery_addresses
|
||||
WHERE via = 'sms'
|
||||
AND id > ?
|
||||
AND value LIKE '+%'
|
||||
ORDER BY id ASC LIMIT ?`,
|
||||
updateQuery: `UPDATE identity_recovery_addresses SET value = ?, updated_at = ? WHERE id = ? AND value = ?`,
|
||||
},
|
||||
}
|
||||
|
||||
startAfterMap, err := parseStartAfter(flagx.MustGetStringSlice(cmd, "start-after"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allStats := make([]normalizeStats, len(tables))
|
||||
for i, table := range tables {
|
||||
startAfter := startAfterMap[table.key]
|
||||
if startAfter != uuid.Nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s: resuming after ID %s\n", table.name, startAfter)
|
||||
}
|
||||
stats, err := normalizeTable(conn, batchSize, batchDelay, startAfter, dryRun, cmd, table)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "normalizing %s", table.name)
|
||||
}
|
||||
allStats[i] = stats
|
||||
}
|
||||
|
||||
printSummary(cmd, tables, allStats)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type tableConfig struct {
|
||||
key string
|
||||
name string
|
||||
selectQuery string
|
||||
updateQuery string
|
||||
}
|
||||
|
||||
// parseStartAfter parses --start-after flags of the form "key=uuid".
|
||||
func parseStartAfter(args []string) (map[string]uuid.UUID, error) {
|
||||
result := make(map[string]uuid.UUID)
|
||||
for _, arg := range args {
|
||||
key, val, ok := strings.Cut(arg, "=")
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid --start-after format %q: expected key=uuid (e.g. credentials=<id>)", arg)
|
||||
}
|
||||
id, err := uuid.FromString(val)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid UUID in --start-after %q", arg)
|
||||
}
|
||||
result[key] = id
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func printSummary(cmd *cobra.Command, tables []tableConfig, allStats []normalizeStats) {
|
||||
out := cmd.OutOrStdout()
|
||||
|
||||
_, _ = fmt.Fprintln(out)
|
||||
|
||||
tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintln(tw, "\tSCANNED\tUPDATED\tSKIPPED\tERRORS")
|
||||
|
||||
var total normalizeStats
|
||||
for i, table := range tables {
|
||||
s := allStats[i]
|
||||
_, _ = fmt.Fprintf(tw, "%s\t%d\t%d\t%d\t%d\n", table.name, s.scanned, s.updated, s.skipped, s.errors)
|
||||
total.scanned += s.scanned
|
||||
total.updated += s.updated
|
||||
total.skipped += s.skipped
|
||||
total.errors += s.errors
|
||||
}
|
||||
_, _ = fmt.Fprintf(tw, "TOTAL\t%d\t%d\t%d\t%d\n", total.scanned, total.updated, total.skipped, total.errors)
|
||||
_ = tw.Flush()
|
||||
}
|
||||
|
||||
func normalizeTable(conn *pop.Connection, batchSize int, batchDelay time.Duration, startAfter uuid.UUID, dryRun bool, cmd *cobra.Command, table tableConfig) (normalizeStats, error) {
|
||||
var stats normalizeStats
|
||||
lastID := startAfter
|
||||
|
||||
for {
|
||||
var rows []struct {
|
||||
ID uuid.UUID `db:"id"`
|
||||
Value string `db:"value"`
|
||||
}
|
||||
|
||||
if err := conn.RawQuery(table.selectQuery, lastID, batchSize).All(&rows); err != nil {
|
||||
return stats, errors.Wrapf(err, "querying %s", table.name)
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
lastID = row.ID
|
||||
stats.scanned++
|
||||
|
||||
// The SQL query pre-filters with LIKE '+%'. This Go-side regex additionally
|
||||
// rejects non-phone identifiers (e.g. OIDC subjects) that happen to start
|
||||
// with '+'. We cannot use SQL regex because SQLite does not support it.
|
||||
if !phonePattern.MatchString(row.Value) {
|
||||
stats.skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
normalized := x.NormalizePhoneIdentifier(row.Value)
|
||||
if normalized == row.Value {
|
||||
stats.skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "[dry-run] %s %s: %q -> %q\n", table.name, row.ID, row.Value, normalized)
|
||||
stats.updated++
|
||||
continue
|
||||
}
|
||||
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
if err := conn.RawQuery(table.updateQuery, normalized, now, row.ID, row.Value).Exec(); err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "error updating %s %s (%q -> %q): %v\n", table.name, row.ID, row.Value, normalized, err)
|
||||
stats.errors++
|
||||
continue
|
||||
}
|
||||
stats.updated++
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\tprocessed %d rows so far (--start-after %s=%s)\n", table.name, stats.scanned, table.key, lastID)
|
||||
|
||||
if batchDelay > 0 {
|
||||
time.Sleep(batchDelay)
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
70
cmd/migrate/normalize_phone_handler_test.go
Normal file
70
cmd/migrate/normalize_phone_handler_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ory/kratos/x"
|
||||
)
|
||||
|
||||
func TestNormalizePhoneIdentifier(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
shouldSkip bool
|
||||
}{
|
||||
{
|
||||
name: "German phone with spaces",
|
||||
input: "+49 176 671 11 638",
|
||||
expected: "+4917667111638",
|
||||
},
|
||||
{
|
||||
name: "German phone with dashes",
|
||||
input: "+49-176-671-11-638",
|
||||
expected: "+4917667111638",
|
||||
},
|
||||
{
|
||||
name: "Already normalized E.164",
|
||||
input: "+4917667111638",
|
||||
expected: "+4917667111638",
|
||||
shouldSkip: true,
|
||||
},
|
||||
{
|
||||
name: "US phone with parens",
|
||||
input: "+1 (415) 555-2671",
|
||||
expected: "+14155552671",
|
||||
},
|
||||
{
|
||||
name: "Malaysian phone with extra zero",
|
||||
input: "+600136866982",
|
||||
expected: "+60136866982",
|
||||
},
|
||||
{
|
||||
name: "Not a valid phone number",
|
||||
input: "+invalid",
|
||||
expected: "+invalid",
|
||||
shouldSkip: true,
|
||||
},
|
||||
{
|
||||
name: "Email address (not a phone)",
|
||||
input: "+foo@bar.com",
|
||||
expected: "+foo@bar.com",
|
||||
shouldSkip: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
normalized := x.NormalizePhoneIdentifier(tc.input)
|
||||
assert.Equal(t, tc.expected, normalized)
|
||||
if tc.shouldSkip {
|
||||
assert.Equal(t, tc.input, normalized, "should be unchanged (skip)")
|
||||
} else {
|
||||
assert.NotEqual(t, tc.input, normalized, "should be changed (update)")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
47
cmd/migrate/root.go
Normal file
47
cmd/migrate/root.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/x/configx"
|
||||
"github.com/ory/x/popx"
|
||||
)
|
||||
|
||||
func NewMigrateCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Various migration helpers",
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterCommandRecursive(parent *cobra.Command) {
|
||||
c := NewMigrateCmd()
|
||||
|
||||
configx.RegisterFlags(c.PersistentFlags())
|
||||
c.AddCommand(NewMigrateSQLCmd())
|
||||
c.AddCommand(NewNormalizePhoneCmd())
|
||||
|
||||
parent.AddCommand(c)
|
||||
}
|
||||
|
||||
func NewMigrateSQLDownCmd(opts ...driver.RegistryOption) *cobra.Command {
|
||||
return popx.NewMigrateSQLDownCmd(func(cmd *cobra.Command, args []string) error {
|
||||
return NewMigrateHandler().MigrateSQLDown(cmd, args, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
func NewMigrateSQLUpCmd(opts ...driver.RegistryOption) *cobra.Command {
|
||||
return popx.NewMigrateSQLUpCmd(func(cmd *cobra.Command, args []string) error {
|
||||
return NewMigrateHandler().MigrateSQLUp(cmd, args, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
func NewMigrateSQLStatusCmd(opts ...driver.RegistryOption) *cobra.Command {
|
||||
return popx.NewMigrateSQLStatusCmd(func(cmd *cobra.Command, args []string) error {
|
||||
return NewMigrateHandler().MigrateSQLStatus(cmd, args, opts...)
|
||||
})
|
||||
}
|
||||
45
cmd/migrate/sql.go
Normal file
45
cmd/migrate/sql.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/x/configx"
|
||||
)
|
||||
|
||||
func NewMigrateSQLCmd(opts ...driver.RegistryOption) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "sql [database-url]",
|
||||
Deprecated: "Please use `kratos migrate sql up|down|status` instead.",
|
||||
Short: "Create SQL schemas and apply migration plans",
|
||||
Long: `Run this command on a fresh SQL installation and when you upgrade Ory Kratos to a new minor version.
|
||||
|
||||
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.
|
||||
|
||||
You can read in the database URL using the -e flag, for example:
|
||||
export DSN=...
|
||||
kratos migrate sql -e
|
||||
|
||||
### WARNING ###
|
||||
|
||||
Before running this command on an existing database, create a back up!
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return NewMigrateHandler().MigrateSQLUp(cmd, args, opts...)
|
||||
},
|
||||
}
|
||||
|
||||
configx.RegisterFlags(c.PersistentFlags())
|
||||
c.PersistentFlags().BoolP("read-from-env", "e", false, "If set, reads the database connection string from the environment variable DSN or config file key dsn.")
|
||||
c.Flags().BoolP("yes", "y", false, "If set all confirmation requests are accepted without user interaction.")
|
||||
|
||||
c.AddCommand(NewMigrateSQLStatusCmd(opts...))
|
||||
c.AddCommand(NewMigrateSQLUpCmd(opts...))
|
||||
c.AddCommand(NewMigrateSQLDownCmd(opts...))
|
||||
|
||||
return c
|
||||
}
|
||||
Reference in New Issue
Block a user