Repository: ory/kratos
Pull request #1474 (2021-06-30 08:12:05)

feat: using ory/x v0.0.256
## Related issue

@aeneasr: this is a follow up for https://github.com/ory/x/pull/356 PR. It just makes use of the corresponding version.

## Checklist

- [x] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md).
- [x] I am following the
      [contributing code guidelines](../blob/master/CONTRIBUTING.md#contributing-code).
- [x] I have read the [security policy](../security/policy).
- [x] I confirm that this pull request does not address a security
      vulnerability. If this pull request addresses a security. vulnerability, I
      confirm that I got green light (please contact
      [security@ory.sh](mailto:security@ory.sh)) from the maintainers to push
      the changes.
- [ ] I have added tests that prove my fix is effective or that my feature
      works.
- [ ] I have added or changed [the documentation](docs/docs).




Patch:
diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go
index 312d7305e..e9d249821 100644
--- a/cmd/clidoc/main.go
+++ b/cmd/clidoc/main.go
@@ -10,7 +10,7 @@ import (
 )
 
 func main() {
-	if err := clidoc.Generate(cmd.RootCmd, os.Args[1:]); err != nil {
+	if err := clidoc.Generate(cmd.NewRootCmd(), os.Args[1:]); err != nil {
 		_, _ = fmt.Fprintf(os.Stderr, "%+v", err)
 		os.Exit(1)
 	}
diff --git a/cmd/courier/root.go b/cmd/courier/root.go
index bdca30a28..815dfea85 100644
--- a/cmd/courier/root.go
+++ b/cmd/courier/root.go
@@ -6,18 +6,18 @@ import (
 	"github.com/ory/x/configx"
 )
 
-// courierCmd represents the courier command
-var courierCmd = &cobra.Command{
-	Use:   "courier",
-	Short: "Commands related to the Ory Kratos message courier",
-}
-
-func init() {
-	configx.RegisterFlags(courierCmd.PersistentFlags())
+// NewCourierCmd creates a new courier command
+func NewCourierCmd() *cobra.Command {
+	c := &cobra.Command{
+		Use:   "courier",
+		Short: "Commands related to the Ory Kratos message courier",
+	}
+	configx.RegisterFlags(c.PersistentFlags())
+	return c
 }
 
 func RegisterCommandRecursive(parent *cobra.Command) {
-	parent.AddCommand(courierCmd)
-
-	courierCmd.AddCommand(watchCmd)
+	c := NewCourierCmd()
+	parent.AddCommand(c)
+	c.AddCommand(NewWatchCmd())
 }
diff --git a/cmd/courier/watch.go b/cmd/courier/watch.go
index 964de0b6a..0405c41af 100644
--- a/cmd/courier/watch.go
+++ b/cmd/courier/watch.go
@@ -15,17 +15,17 @@ import (
 	"github.com/ory/x/reqlog"
 )
 
-var watchCmd = &cobra.Command{
-	Use:   "watch",
-	Short: "Starts the Ory Kratos message courier",
-	Run: func(cmd *cobra.Command, args []string) {
-		r := driver.New(cmd.Context(), configx.WithFlags(cmd.Flags()))
-		StartCourier(cmd.Context(), r)
-	},
-}
-
-func init() {
-	watchCmd.PersistentFlags().Int("expose-metrics-port", 0, "The port to expose the metrics endpoint on (not exposed by default)")
+func NewWatchCmd() *cobra.Command {
+	var c = &cobra.Command{
+		Use:   "watch",
+		Short: "Starts the Ory Kratos message courier",
+		Run: func(cmd *cobra.Command, args []string) {
+			r := driver.New(cmd.Context(), configx.WithFlags(cmd.Flags()))
+			StartCourier(cmd.Context(), r)
+		},
+	}
+	c.PersistentFlags().Int("expose-metrics-port", 0, "The port to expose the metrics endpoint on (not exposed by default)")
+	return c
 }
 
 func StartCourier(ctx cx.Context, r driver.Registry) {
@@ -83,7 +83,6 @@ func ServeMetrics(ctx cx.Context, r driver.Registry) {
 }
 
 func Watch(ctx cx.Context, r driver.Registry) {
-
 	ctx, cancel := cx.WithCancel(ctx)
 
 	r.Logger().Println("Courier worker started.")
@@ -97,5 +96,4 @@ func Watch(ctx cx.Context, r driver.Registry) {
 	}
 
 	r.Logger().Println("Courier worker was shutdown gracefully.")
-
 }
diff --git a/cmd/hashers/root.go b/cmd/hashers/root.go
index f42c2a9d8..0c50e4356 100644
--- a/cmd/hashers/root.go
+++ b/cmd/hashers/root.go
@@ -6,13 +6,16 @@ import (
 	"github.com/ory/kratos/cmd/hashers/argon2"
 )
 
-var rootCmd = &cobra.Command{
-	Use:   "hashers",
-	Short: "This command contains helpers around hashing",
+func NewRootCmd() *cobra.Command {
+	c := &cobra.Command{
+		Use:   "hashers",
+		Short: "This command contains helpers around hashing",
+	}
+	return c
 }
 
 func RegisterCommandRecursive(parent *cobra.Command) {
-	parent.AddCommand(rootCmd)
+	parent.AddCommand(NewRootCmd())
 
-	argon2.RegisterCommandRecursive(rootCmd)
+	argon2.RegisterCommandRecursive(NewRootCmd())
 }
diff --git a/cmd/identities/delete.go b/cmd/identities/delete.go
index eed9447ac..9b01b0593 100644
--- a/cmd/identities/delete.go
+++ b/cmd/identities/delete.go
@@ -10,45 +10,47 @@ import (
 	"github.com/ory/x/cmdx"
 )
 
-var DeleteCmd = &cobra.Command{
-	Use:   "delete <id-0 [id-1 ...]>",
-	Short: "Delete identities by ID",
-	Long: fmt.Sprintf(`This command deletes one or more identities by ID. To delete an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
+func NewDeleteCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "delete <id-0 [id-1 ...]>",
+		Short: "Delete identities by ID",
+		Long: fmt.Sprintf(`This command deletes one or more identities by ID. To delete an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
 
 %s
 `, clihelpers.WarningJQIsComplicated),
-	Example: `To delete the identity with the recovery email address "foo@bar.com", run:
+		Example: `To delete the identity with the recovery email address "foo@bar.com", run:
 
 	$ kratos identities delete $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value == "foo@bar.com")) | .[].id')`,
-	Args: cobra.MinimumNArgs(1),
-	RunE: func(cmd *cobra.Command, args []string) error {
-		c := cliclient.NewClient(cmd)
-
-		var (
-			deleted = make([]string, 0, len(args))
-			errs    []error
-		)
-
-		for _, a := range args {
-			_, err := c.AdminApi.DeleteIdentity(cmd.Context(), a).Execute()
-			if err != nil {
-				errs = append(errs, err)
-				continue
+		Args: cobra.MinimumNArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			c := cliclient.NewClient(cmd)
+
+			var (
+				deleted = make([]string, 0, len(args))
+				errs    []error
+			)
+
+			for _, a := range args {
+				_, err := c.AdminApi.DeleteIdentity(cmd.Context(), a).Execute()
+				if err != nil {
+					errs = append(errs, err)
+					continue
+				}
+				deleted = append(deleted, a)
 			}
-			deleted = append(deleted, a)
-		}
-
-		for _, d := range deleted {
-			_, _ = fmt.Fprintln(cmd.OutOrStdout(), d)
-		}
-
-		for _, err := range errs {
-			_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%+v\n", err)
-		}
-
-		if len(errs) != 0 {
-			return cmdx.FailSilently(cmd)
-		}
-		return nil
-	},
+
+			for _, d := range deleted {
+				_, _ = fmt.Fprintln(cmd.OutOrStdout(), d)
+			}
+
+			for _, err := range errs {
+				_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%+v\n", err)
+			}
+
+			if len(errs) != 0 {
+				return cmdx.FailSilently(cmd)
+			}
+			return nil
+		},
+	}
 }
diff --git a/cmd/identities/get.go b/cmd/identities/get.go
index c854464b3..fe14fbc76 100644
--- a/cmd/identities/get.go
+++ b/cmd/identities/get.go
@@ -14,42 +14,44 @@ import (
 	"github.com/ory/kratos/cmd/cliclient"
 )
 
-var GetCmd = &cobra.Command{
-	Use:   "get <id-0 [id-1 ...]>",
-	Short: "Get one or more identities by ID",
-	Long: fmt.Sprintf(`This command gets all the details about an identity. To get an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
+func NewGetCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "get <id-0 [id-1 ...]>",
+		Short: "Get one or more identities by ID",
+		Long: fmt.Sprintf(`This command gets all the details about an identity. To get an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
 
 %s
 `, clihelpers.WarningJQIsComplicated),
-	Example: `To get the identities with the recovery email address at the domain "ory.sh", run:
+		Example: `To get the identities with the recovery email address at the domain "ory.sh", run:
 
 	$ kratos identities get $(kratos identities list --format json | jq -r 'map(select(.recovery_addresses[].value | endswith("@ory.sh"))) | .[].id')`,
-	Args: cobra.MinimumNArgs(1),
-	RunE: func(cmd *cobra.Command, args []string) error {
-		c := cliclient.NewClient(cmd)
-
-		identities := make([]kratos.Identity, 0, len(args))
-		failed := make(map[string]error)
-		for _, id := range args {
-			identity, _, err := c.AdminApi.GetIdentity(cmd.Context(), id).Execute()
-			if x.SDKError(err) != nil {
-				failed[id] = err
-				continue
+		Args: cobra.MinimumNArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			c := cliclient.NewClient(cmd)
+
+			identities := make([]kratos.Identity, 0, len(args))
+			failed := make(map[string]error)
+			for _, id := range args {
+				identity, _, err := c.AdminApi.GetIdentity(cmd.Context(), id).Execute()
+				if x.SDKError(err) != nil {
+					failed[id] = err
+					continue
+				}
+
+				identities = append(identities, *identity)
 			}
 
-			identities = append(identities, *identity)
-		}
-
-		if len(identities) == 1 {
-			cmdx.PrintRow(cmd, (*outputIdentity)(&identities[0]))
-		} else if len(identities) > 1 {
-			cmdx.PrintTable(cmd, &outputIdentityCollection{identities})
-		}
-		cmdx.PrintErrors(cmd, failed)
-
-		if len(failed) != 0 {
-			return cmdx.FailSilently(cmd)
-		}
-		return nil
-	},
+			if len(identities) == 1 {
+				cmdx.PrintRow(cmd, (*outputIdentity)(&identities[0]))
+			} else if len(identities) > 1 {
+				cmdx.PrintTable(cmd, &outputIdentityCollection{identities})
+			}
+			cmdx.PrintErrors(cmd, failed)
+
+			if len(failed) != 0 {
+				return cmdx.FailSilently(cmd)
+			}
+			return nil
+		},
+	}
 }
diff --git a/cmd/identities/helpers.go b/cmd/identities/helpers.go
index a7a11cacf..aabe9770b 100644
--- a/cmd/identities/helpers.go
+++ b/cmd/identities/helpers.go
@@ -1,28 +1,13 @@
 package identities
 
 import (
-	"bytes"
-	"context"
 	"fmt"
-	"io"
 	"io/ioutil"
-	"testing"
-
-	"github.com/ory/x/cmdx"
-
-	"github.com/pkg/errors"
-	"github.com/tidwall/gjson"
-
-	"github.com/ory/kratos/identity"
 
 	"github.com/spf13/cobra"
-	"github.com/stretchr/testify/require"
+	"github.com/tidwall/gjson"
 
-	"github.com/ory/kratos/cmd/cliclient"
-	"github.com/ory/kratos/driver"
-	"github.com/ory/kratos/driver/config"
-	"github.com/ory/kratos/internal"
-	"github.com/ory/kratos/internal/testhelpers"
+	"github.com/ory/x/cmdx"
 )
 
 func parseIdentities(raw []byte) (rawIdentities []string) {
@@ -62,53 +47,3 @@ func readIdentities(cmd *cobra.Command, args []string) (map[string]string, error
 	}
 	return rawIdentities, nil
 }
-
-func setup(t *testing.T, cmd *cobra.Command) driver.Registry {
-	conf, reg := internal.NewFastRegistryWithMocks(t)
-	_, admin := testhelpers.NewKratosServerWithCSRF(t, reg)
-	conf.MustSet(config.ViperKeyDefaultIdentitySchemaURL, "file://./stubs/identity.schema.json")
-	// setup command
-	cliclient.RegisterClientFlags(cmd.Flags())
-	cmdx.RegisterFormatFlags(cmd.Flags())
-	require.NoError(t, cmd.Flags().Set(cliclient.FlagEndpoint, admin.URL))
-	require.NoError(t, cmd.Flags().Set(cmdx.FlagFormat, string(cmdx.FormatJSON)))
-	return reg
-}
-
-func exec(cmd *cobra.Command, stdIn io.Reader, args ...string) (string, string, error) {
-	stdOut, stdErr := &bytes.Buffer{}, &bytes.Buffer{}
-	cmd.SetErr(stdErr)
-	cmd.SetOut(stdOut)
-	cmd.SetIn(stdIn)
-	defer cmd.SetIn(nil)
-	if args == nil {
-		args = []string{}
-	}
-	cmd.SetArgs(args)
-	err := cmd.Execute()
-	return stdOut.String(), stdErr.String(), err
-}
-
-func execNoErr(t *testing.T, cmd *cobra.Command, args ...string) string {
-	stdOut, stdErr, err := exec(cmd, nil, args...)
-	require.NoError(t, err)
-	require.Len(t, stdErr, 0, stdOut)
-	return stdOut
-}
-
-func execErr(t *testing.T, cmd *cobra.Command, args ...string) string {
-	stdOut, stdErr, err := exec(cmd, nil, args...)
-	require.True(t, errors.Is(err, cmdx.ErrNoPrintButFail))
-	require.Len(t, stdOut, 0, stdErr)
-	return stdErr
-}
-
-func makeIdentities(t *testing.T, reg driver.Registry, n int) (is []*identity.Identity, ids []string) {
-	for j := 0; j < n; j++ {
-		i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
-		require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
-		is = append(is, i)
-		ids = append(ids, i.ID.String())
-	}
-	return
-}
diff --git a/cmd/identities/import.go b/cmd/identities/import.go
index 6ff21cada..e11aa8895 100644
--- a/cmd/identities/import.go
+++ b/cmd/identities/import.go
@@ -15,11 +15,12 @@ import (
 	"github.com/ory/kratos/cmd/cliclient"
 )
 
-// ImportCmd represents the import command
-var ImportCmd = &cobra.Command{
-	Use:   "import <file.json [file-2.json [file-3.json] ...]>",
-	Short: "Import identities from files or STD_IN",
-	Example: `$ cat > ./file.json <<EOF
+// NewImportCmd represents the import command
+func NewImportCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "import <file.json [file-2.json [file-3.json] ...]>",
+		Short: "Import identities from files or STD_IN",
+		Example: `$ cat > ./file.json <<EOF
 {
     "schema_id": "default",
     "traits": {
@@ -31,55 +32,56 @@ EOF
 $ kratos identities import file.json
 # Alternatively:
 $ cat file.json | kratos identities import`,
-	Long: `Import identities from files or STD_IN.
+		Long: `Import identities from files or STD_IN.
 
 Files can contain only a single or an array of identities. The validity of files can be tested beforehand using "... identities validate".
 
 WARNING: Importing credentials is not yet supported.`,
-	RunE: func(cmd *cobra.Command, args []string) error {
-		c := cliclient.NewClient(cmd)
+		RunE: func(cmd *cobra.Command, args []string) error {
+			c := cliclient.NewClient(cmd)
 
-		imported := make([]kratos.Identity, 0, len(args))
-		failed := make(map[string]error)
+			imported := make([]kratos.Identity, 0, len(args))
+			failed := make(map[string]error)
 
-		is, err := readIdentities(cmd, args)
-		if err != nil {
-			return err
-		}
-
-		for src, i := range is {
-			err = validateIdentity(cmd, src, i, func(ctx context.Context, id string) (map[string]interface{}, *http.Response, error) {
-				return c.PublicApi.GetSchema(ctx, id).Execute()
-			})
+			is, err := readIdentities(cmd, args)
 			if err != nil {
 				return err
 			}
 
-			var params kratos.CreateIdentity
-			err = json.Unmarshal([]byte(i), &params)
-			if err != nil {
-				_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "STD_IN: Could not parse identity")
-				return cmdx.FailSilently(cmd)
+			for src, i := range is {
+				err = ValidateIdentity(cmd, src, i, func(ctx context.Context, id string) (map[string]interface{}, *http.Response, error) {
+					return c.PublicApi.GetSchema(ctx, id).Execute()
+				})
+				if err != nil {
+					return err
+				}
+
+				var params kratos.CreateIdentity
+				err = json.Unmarshal([]byte(i), &params)
+				if err != nil {
+					_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "STD_IN: Could not parse identity")
+					return cmdx.FailSilently(cmd)
+				}
+
+				ident, _, err := c.AdminApi.CreateIdentity(cmd.Context()).CreateIdentity(params).Execute()
+				if err != nil {
+					failed[src] = err
+				} else {
+					imported = append(imported, *ident)
+				}
 			}
-
-			ident, _, err := c.AdminApi.CreateIdentity(cmd.Context()).CreateIdentity(params).Execute()
-			if err != nil {
-				failed[src] = err
+			if len(imported) == 1 {
+				cmdx.PrintRow(cmd, (*outputIdentity)(&imported[0]))
 			} else {
-				imported = append(imported, *ident)
+				cmdx.PrintTable(cmd, &outputIdentityCollection{identities: imported})
 			}
-		}
-		if len(imported) == 1 {
-			cmdx.PrintRow(cmd, (*outputIdentity)(&imported[0]))
-		} else {
-			cmdx.PrintTable(cmd, &outputIdentityCollection{identities: imported})
-		}
-		cmdx.PrintErrors(cmd, failed)
-
-		if len(failed) != 0 {
-			return cmdx.FailSilently(cmd)
-		}
-
-		return nil
-	},
+			cmdx.PrintErrors(cmd, failed)
+
+			if len(failed) != 0 {
+				return cmdx.FailSilently(cmd)
+			}
+
+			return nil
+		},
+	}
 }
diff --git a/cmd/identities/list.go b/cmd/identities/list.go
index 60c254683..a571fc382 100644
--- a/cmd/identities/list.go
+++ b/cmd/identities/list.go
@@ -11,48 +11,50 @@ import (
 	"github.com/ory/kratos/cmd/cliclient"
 )
 
-var ListCmd = &cobra.Command{
-	Use:   "list [<page> <per-page>]",
-	Short: "List identities",
-	Long:  "List identities (paginated)",
-	Args: func(cmd *cobra.Command, args []string) error {
-		// zero or exactly two args
-		if len(args) != 0 && len(args) != 2 {
-			return fmt.Errorf("expected zero or two args, got %d: %+v", len(args), args)
-		}
-		return nil
-	},
-	Aliases: []string{"ls"},
-	RunE: func(cmd *cobra.Command, args []string) error {
-		c := cliclient.NewClient(cmd)
-		req := c.AdminApi.ListIdentities(cmd.Context())
-
-		if len(args) == 2 {
-			page, err := strconv.ParseInt(args[0], 0, 64)
-			if err != nil {
-				_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse page argument\"%s\": %s", args[0], err)
-				return cmdx.FailSilently(cmd)
+func NewListCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "list [<page> <per-page>]",
+		Short: "List identities",
+		Long:  "List identities (paginated)",
+		Args: func(cmd *cobra.Command, args []string) error {
+			// zero or exactly two args
+			if len(args) != 0 && len(args) != 2 {
+				return fmt.Errorf("expected zero or two args, got %d: %+v", len(args), args)
+			}
+			return nil
+		},
+		Aliases: []string{"ls"},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			c := cliclient.NewClient(cmd)
+			req := c.AdminApi.ListIdentities(cmd.Context())
+
+			if len(args) == 2 {
+				page, err := strconv.ParseInt(args[0], 0, 64)
+				if err != nil {
+					_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse page argument\"%s\": %s", args[0], err)
+					return cmdx.FailSilently(cmd)
+				}
+				req = req.Page(page)
+
+				perPage, err := strconv.ParseInt(args[1], 0, 64)
+				if err != nil {
+					_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse per-page argument\"%s\": %s", args[1], err)
+					return cmdx.FailSilently(cmd)
+				}
+				req = req.PerPage(perPage)
 			}
-			req = req.Page(page)
 
-			perPage, err := strconv.ParseInt(args[1], 0, 64)
+			identities, _, err := req.Execute()
 			if err != nil {
-				_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse per-page argument\"%s\": %s", args[1], err)
+				_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get the identities: %+v\n", err)
 				return cmdx.FailSilently(cmd)
 			}
-			req = req.PerPage(perPage)
-		}
-
-		identities, _, err := req.Execute()
-		if err != nil {
-			_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not get the identities: %+v\n", err)
-			return cmdx.FailSilently(cmd)
-		}
 
-		cmdx.PrintTable(cmd, &outputIdentityCollection{
-			identities: identities,
-		})
+			cmdx.PrintTable(cmd, &outputIdentityCollection{
+				identities: identities,
+			})
 
-		return nil
-	},
+			return nil
+		},
+	}
 }
diff --git a/cmd/identities/patch.go b/cmd/identities/patch.go
index 44be38fa1..f9f886bb3 100644
--- a/cmd/identities/patch.go
+++ b/cmd/identities/patch.go
@@ -7,13 +7,15 @@ import (
 	"github.com/spf13/cobra"
 )
 
-var PatchCmd = &cobra.Command{
-	Use:   "patch <file.json [file-2.json [file-3.json] ...]>",
-	Short: "Patch identities by ID (not yet implemented)",
-	Args:  cobra.MinimumNArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		// TODO
-		fmt.Println("not yet implemented")
-		os.Exit(1)
-	},
+func NewPatchCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "patch <file.json [file-2.json [file-3.json] ...]>",
+		Short: "Patch identities by ID (not yet implemented)",
+		Args:  cobra.MinimumNArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			// TODO
+			fmt.Println("not yet implemented")
+			os.Exit(1)
+		},
+	}
 }
diff --git a/cmd/identities/root.go b/cmd/identities/root.go
index 2c83d8008..d5c19c44f 100644
--- a/cmd/identities/root.go
+++ b/cmd/identities/root.go
@@ -8,24 +8,26 @@ import (
 	"github.com/ory/kratos/cmd/cliclient"
 )
 
-// identitiesCmd represents the identity command
-var identitiesCmd = &cobra.Command{
-	Use:   "identities",
-	Short: "Tools to interact with remote identities",
+// NewIdentitiesCmd represents the identity command
+func NewIdentitiesCmd() *cobra.Command {
+	var identitiesCmd = &cobra.Command{
+		Use:   "identities",
+		Short: "Tools to interact with remote identities",
+	}
+
+	cliclient.RegisterClientFlags(identitiesCmd.PersistentFlags())
+	cmdx.RegisterFormatFlags(identitiesCmd.PersistentFlags())
+	return identitiesCmd
 }
 
 func RegisterCommandRecursive(parent *cobra.Command) {
-	parent.AddCommand(identitiesCmd)
+	c := NewIdentitiesCmd()
+	parent.AddCommand(c)
 
-	identitiesCmd.AddCommand(ImportCmd)
-	identitiesCmd.AddCommand(ValidateCmd)
-	identitiesCmd.AddCommand(ListCmd)
-	identitiesCmd.AddCommand(GetCmd)
-	identitiesCmd.AddCommand(DeleteCmd)
-	identitiesCmd.AddCommand(PatchCmd)
-}
-
-func RegisterFlags() {
-	cliclient.RegisterClientFlags(identitiesCmd.PersistentFlags())
-	cmdx.RegisterFormatFlags(identitiesCmd.PersistentFlags())
+	c.AddCommand(NewImportCmd())
+	c.AddCommand(NewValidateCmd())
+	c.AddCommand(NewListCmd())
+	c.AddCommand(NewGetCmd())
+	c.AddCommand(NewDeleteCmd())
+	c.AddCommand(NewPatchCmd())
 }
diff --git a/cmd/identities/validate.go b/cmd/identities/validate.go
index 9df205b01..fa2279f03 100644
--- a/cmd/identities/validate.go
+++ b/cmd/identities/validate.go
@@ -21,45 +21,49 @@ import (
 	"github.com/ory/kratos/cmd/cliclient"
 )
 
-var ValidateCmd = &cobra.Command{
-	Use:   "validate <file.json [file-2.json [file-3.json] ...]>",
-	Short: "Validate local identity files",
-	Long: `This command allows validation of identity files.
+func NewValidateCmd() *cobra.Command {
+	var c = &cobra.Command{
+		Use:   "validate <file.json [file-2.json [file-3.json] ...]>",
+		Short: "Validate local identity files",
+		Long: `This command allows validation of identity files.
 It validates against the payload of the API and the identity schema as configured in Ory Kratos.
 Identities can be supplied via STD_IN or JSON files containing a single or an array of identities.
 `,
-	RunE: func(cmd *cobra.Command, args []string) error {
-		c := cliclient.NewClient(cmd)
+		RunE: func(cmd *cobra.Command, args []string) error {
+			c := cliclient.NewClient(cmd)
 
-		is, err := readIdentities(cmd, args)
-		if err != nil {
-			return err
-		}
-
-		for src, i := range is {
-			err = validateIdentity(cmd, src, i, func(ctx context.Context, id string) (map[string]interface{}, *http.Response, error) {
-				return c.PublicApi.GetSchema(ctx, id).Execute()
-			})
+			is, err := readIdentities(cmd, args)
 			if err != nil {
 				return err
 			}
-		}
 
-		_, _ = fmt.Fprintln(cmd.OutOrStdout(), "All identity files are valid.")
-		return nil
-	},
+			for src, i := range is {
+				err = ValidateIdentity(cmd, src, i, func(ctx context.Context, id string) (map[string]interface{}, *http.Response, error) {
+					return c.PublicApi.GetSchema(ctx, id).Execute()
+				})
+				if err != nil {
+					return err
+				}
+			}
+
+			_, _ = fmt.Fprintln(cmd.OutOrStdout(), "All identity files are valid.")
+			return nil
+		},
+	}
+
+	return c
 }
 
 var schemas = make(map[string]*jsonschema.Schema)
 
 const createIdentityPath = "openapi.json#/components/schemas/createIdentity"
 
-type schemaGetter = func(ctx context.Context, id string) (map[string]interface{}, *http.Response, error)
+type SchemaGetter = func(ctx context.Context, id string) (map[string]interface{}, *http.Response, error)
 
-// validateIdentity validates the json payload fc against
+// ValidateIdentity validates the json payload fc against
 // 1. the swagger payload definition and
 // 2. the remote custom identity schema.
-func validateIdentity(cmd *cobra.Command, src, i string, getRemoteSchema schemaGetter) error {
+func ValidateIdentity(cmd *cobra.Command, src, i string, getRemoteSchema SchemaGetter) error {
 	swaggerSchema, ok := schemas[createIdentityPath]
 	if !ok {
 		// add swagger schema
diff --git a/cmd/jsonnet/format.go b/cmd/jsonnet/format.go
index 5b68f011d..14297a718 100644
--- a/cmd/jsonnet/format.go
+++ b/cmd/jsonnet/format.go
@@ -1,18 +1,3 @@
-/*
-Copyright © 2020 NAME HERE <EMAIL ADDRESS>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
 package jsonnet
 
 import (
@@ -27,39 +12,38 @@ import (
 	"github.com/ory/x/flagx"
 )
 
-// jsonnetFormatCmd represents the format command
-var jsonnetFormatCmd = &cobra.Command{
-	Use: "format path/to/files/*.jsonnet [more/files.jsonnet, [supports/**/{foo,bar}.jsonnet]]",
-	Long: `Formats JSONNet files using the official JSONNet formatter.
+func NewJsonnetFormatCmd() *cobra.Command {
+	c := &cobra.Command{
+		Use: "format path/to/files/*.jsonnet [more/files.jsonnet, [supports/**/{foo,bar}.jsonnet]]",
+		Long: `Formats JSONNet files using the official JSONNet formatter.
 
 Use -w or --write to write output back to files instead of stdout.
 
 ` + GlobHelp,
-	Args: cobra.MinimumNArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		for _, pattern := range args {
-			files, err := filepath.Glob(pattern)
-			cmdx.Must(err, `Glob path "%s" is not valid: %s`, pattern, err)
-
-			shouldWrite := flagx.MustGetBool(cmd, "write")
-			for _, file := range files {
-				content, err := ioutil.ReadFile(file)
-				cmdx.Must(err, `Unable to read file "%s" because: %s`, file, err)
-
-				output, err := formatter.Format(file, string(content), formatter.DefaultOptions())
-				cmdx.Must(err, `JSONNet file "%s" could not be formatted: %s`, file, err)
-
-				if shouldWrite {
-					err := ioutil.WriteFile(file, []byte(output), 0644) // #nosec
-					cmdx.Must(err, `Could not write to file "%s" because: %s`, file, err)
-				} else {
-					fmt.Println(output)
+		Args: cobra.MinimumNArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			for _, pattern := range args {
+				files, err := filepath.Glob(pattern)
+				cmdx.Must(err, `Glob path "%s" is not valid: %s`, pattern, err)
+
+				shouldWrite := flagx.MustGetBool(cmd, "write")
+				for _, file := range files {
+					content, err := ioutil.ReadFile(file)
+					cmdx.Must(err, `Unable to read file "%s" because: %s`, file, err)
+
+					output, err := formatter.Format(file, string(content), formatter.DefaultOptions())
+					cmdx.Must(err, `JSONNet file "%s" could not be formatted: %s`, file, err)
+
+					if shouldWrite {
+						err := ioutil.WriteFile(file, []byte(output), 0644) // #nosec
+						cmdx.Must(err, `Could not write to file "%s" because: %s`, file, err)
+					} else {
+						fmt.Println(output)
+					}
 				}
 			}
-		}
-	},
-}
-
-func init() {
-	jsonnetFormatCmd.Flags().BoolP("write", "w", false, "Write formatted output back to file.")
+		},
+	}
+	c.Flags().BoolP("write", "w", false, "Write formatted output back to file.")
+	return c
 }
diff --git a/cmd/jsonnet/lint.go b/cmd/jsonnet/lint.go
index 166f0f56d..bb181ae6d 100644
--- a/cmd/jsonnet/lint.go
+++ b/cmd/jsonnet/lint.go
@@ -14,32 +14,33 @@ import (
 	"github.com/ory/x/cmdx"
 )
 
-// jsonnetLintCmd represents the lint command
-var jsonnetLintCmd = &cobra.Command{
-	Use: "lint path/to/files/*.jsonnet [more/files.jsonnet, [supports/**/{foo,bar}.jsonnet]]",
-	Long: `Lints JSONNet files using the official JSONNet linter and exits with a status code of 1 when issues are detected.
+func NewJsonnetLintCmd() *cobra.Command {
+	return &cobra.Command{
+		Use: "lint path/to/files/*.jsonnet [more/files.jsonnet, [supports/**/{foo,bar}.jsonnet]]",
+		Long: `Lints JSONNet files using the official JSONNet linter and exits with a status code of 1 when issues are detected.
 
 ` + GlobHelp,
-	Args: cobra.MinimumNArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		vm := jsonnet.MakeVM()
+		Args: cobra.MinimumNArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			vm := jsonnet.MakeVM()
 
-		for _, pattern := range args {
-			files, err := filepath.Glob(pattern)
-			cmdx.Must(err, `Glob path "%s" is not valid: %s`, pattern, err)
+			for _, pattern := range args {
+				files, err := filepath.Glob(pattern)
+				cmdx.Must(err, `Glob path "%s" is not valid: %s`, pattern, err)
 
-			for _, file := range files {
-				content, err := ioutil.ReadFile(file)
-				cmdx.Must(err, `Unable to read file "%s" because: %s`, file, err)
+				for _, file := range files {
+					content, err := ioutil.ReadFile(file)
+					cmdx.Must(err, `Unable to read file "%s" because: %s`, file, err)
 
-				var outBuilder strings.Builder
-				errorsFound := linter.LintSnippet(vm, &outBuilder, file, string(content))
+					var outBuilder strings.Builder
+					errorsFound := linter.LintSnippet(vm, &outBuilder, file, string(content))
 
-				if errorsFound {
-					_, _ = fmt.Fprintf(os.Stderr, "Linter found issues.")
-					os.Exit(1)
+					if errorsFound {
+						_, _ = fmt.Fprintf(os.Stderr, "Linter found issues.")
+						os.Exit(1)
+					}
 				}
 			}
-		}
-	},
+		},
+	}
 }
diff --git a/cmd/jsonnet/root.go b/cmd/jsonnet/root.go
index 06eb6827a..62442ee6b 100644
--- a/cmd/jsonnet/root.go
+++ b/cmd/jsonnet/root.go
@@ -29,15 +29,19 @@ const GlobHelp = `Glob Syntax:
         pattern { ',' pattern }
                     comma-separated (without spaces) patterns`
 
-// jsonnetCmd represents the jsonnet command
-var jsonnetCmd = &cobra.Command{
-	Use:   "jsonnet",
-	Short: "Helpers for linting and formatting JSONNet code",
+func NewJsonnetCmd() *cobra.Command {
+	c := &cobra.Command{
+		Use:   "jsonnet",
+		Short: "Helpers for linting and formatting JSONNet code",
+	}
+
+	return c
 }
 
 func RegisterCommandRecursive(parent *cobra.Command) {
-	parent.AddCommand(jsonnetCmd)
+	c := NewJsonnetCmd()
+	parent.AddCommand(c)
 
-	jsonnetCmd.AddCommand(jsonnetFormatCmd)
-	jsonnetCmd.AddCommand(jsonnetLintCmd)
+	c.AddCommand(NewJsonnetFormatCmd())
+	c.AddCommand(NewJsonnetLintCmd())
 }
diff --git a/cmd/root.go b/cmd/root.go
index 02c6177bb..5172f86be 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -22,31 +22,33 @@ import (
 )
 
 // RootCmd represents the base command when called without any subcommands
-var RootCmd = &cobra.Command{
-	Use: "kratos",
+func NewRootCmd() (cmd *cobra.Command) {
+	cmd = &cobra.Command{
+		Use: "kratos",
+	}
+	identities.RegisterCommandRecursive(cmd)
+
+	jsonnet.RegisterCommandRecursive(cmd)
+	serve.RegisterCommandRecursive(cmd)
+	migrate.RegisterCommandRecursive(cmd)
+	remote.RegisterCommandRecursive(cmd)
+	hashers.RegisterCommandRecursive(cmd)
+	courier.RegisterCommandRecursive(cmd)
+
+	cmd.AddCommand(cmdx.Version(&config.Version, &config.Commit, &config.Date))
+
+	return cmd
 }
 
 // Execute adds all child commands to the root command and sets flags appropriately.
 // This is called by main.main(). It only needs to happen once to the RootCmd.
 func Execute() {
-	if err := RootCmd.Execute(); err != nil {
+	c := NewRootCmd()
+
+	if err := c.Execute(); err != nil {
 		if !errors.Is(err, cmdx.ErrNoPrintButFail) {
-			_, _ = fmt.Fprintln(RootCmd.ErrOrStderr(), err)
+			_, _ = fmt.Fprintln(c.ErrOrStderr(), err)
 		}
 		os.Exit(1)
 	}
 }
-
-func init() {
-	identities.RegisterCommandRecursive(RootCmd)
-	identities.RegisterFlags()
-
-	jsonnet.RegisterCommandRecursive(RootCmd)
-	serve.RegisterCommandRecursive(RootCmd)
-	migrate.RegisterCommandRecursive(RootCmd)
-	remote.RegisterCommandRecursive(RootCmd)
-	hashers.RegisterCommandRecursive(RootCmd)
-	courier.RegisterCommandRecursive(RootCmd)
-
-	RootCmd.AddCommand(cmdx.Version(&config.Version, &config.Commit, &config.Date))
-}
diff --git a/cmd/serve/root.go b/cmd/serve/root.go
index d3ee01903..e05899a51 100644
--- a/cmd/serve/root.go
+++ b/cmd/serve/root.go
@@ -25,42 +25,42 @@ import (
 )
 
 // serveCmd represents the serve command
-var serveCmd = &cobra.Command{
-	Use:   "serve",
-	Short: "Run the Ory Kratos server",
-	Run: func(cmd *cobra.Command, args []string) {
-		d := driver.New(cmd.Context(), configx.WithFlags(cmd.Flags()))
+func NewServeCmd() (serveCmd *cobra.Command) {
+	serveCmd = &cobra.Command{
+		Use:   "serve",
+		Short: "Run the Ory Kratos server",
+		Run: func(cmd *cobra.Command, args []string) {
+			d := driver.New(cmd.Context(), configx.WithFlags(cmd.Flags()))
 
-		if d.Config(cmd.Context()).IsInsecureDevMode() {
-			d.Logger().Warn(`
+			if d.Config(cmd.Context()).IsInsecureDevMode() {
+				d.Logger().Warn(`
 
 YOU ARE RUNNING Ory KRATOS IN DEV MODE.
 SECURITY IS DISABLED.
 DON'T DO THIS IN PRODUCTION!
 
 `)
-		}
+			}
 
-		configVersion := d.Config(cmd.Context()).ConfigVersion()
-		if configVersion == config.UnknownVersion {
-			d.Logger().Warn("The config has no version specified. Add the version to improve your development experience.")
-		} else if config.Version != "" &&
-			configVersion != config.Version {
-			d.Logger().Warnf("Config version is '%s' but kratos runs on version '%s'", configVersion, config.Version)
-		}
+			configVersion := d.Config(cmd.Context()).ConfigVersion()
+			if configVersion == config.UnknownVersion {
+				d.Logger().Warn("The config has no version specified. Add the version to improve your development experience.")
+			} else if config.Version != "" &&
+				configVersion != config.Version {
+				d.Logger().Warnf("Config version is '%s' but kratos runs on version '%s'", configVersion, config.Version)
+			}
 
-		daemon.ServeAll(d)(cmd, args)
-	},
-}
-
-func RegisterCommandRecursive(parent *cobra.Command) {
-	parent.AddCommand(serveCmd)
-}
-
-func init() {
+			daemon.ServeAll(d)(cmd, args)
+		},
+	}
 	configx.RegisterFlags(serveCmd.PersistentFlags())
 
 	serveCmd.PersistentFlags().Bool("sqa-opt-out", false, "Disable anonymized telemetry reports - for more information please visit https://www.ory.sh/docs/ecosystem/sqa")
 	serveCmd.PersistentFlags().Bool("dev", false, "Disables critical security features to make development easier")
 	serveCmd.PersistentFlags().Bool("watch-courier", false, "Run the message courier as a background task, to simplify single-instance setup")
+	return serveCmd
+}
+
+func RegisterCommandRecursive(parent *cobra.Command) {
+	parent.AddCommand(NewServeCmd())
 }
diff --git a/docs/docs/cli/kratos.md b/docs/docs/cli/kratos.md
index 5758d087f..796bee9d5 100644
--- a/docs/docs/cli/kratos.md
+++ b/docs/docs/cli/kratos.md
@@ -22,8 +22,6 @@ To improve this file please make your change against the appropriate "./cmd/*.go
 
 - [kratos courier](kratos-courier) - Commands related to the Ory Kratos message
   courier
-- [kratos hashers](kratos-hashers) - This command contains helpers around
-  hashing
 - [kratos identities](kratos-identities) - Tools to interact with remote
   identities
 - [kratos jsonnet](kratos-jsonnet) - Helpers for linting and formatting JSONNet
diff --git a/docs/sidebar.json b/docs/sidebar.json
index 7a9a01bdd..aaa9e25e6 100644
--- a/docs/sidebar.json
+++ b/docs/sidebar.json
@@ -78,11 +78,6 @@
           "cli/kratos",
           "cli/kratos-courier",
           "cli/kratos-courier-watch",
-          "cli/kratos-hashers",
-          "cli/kratos-hashers-argon2",
-          "cli/kratos-hashers-argon2-calibrate",
-          "cli/kratos-hashers-argon2-hash",
-          "cli/kratos-hashers-argon2-load-test",
           "cli/kratos-identities",
           "cli/kratos-identities-delete",
           "cli/kratos-identities-get",
diff --git a/go.mod b/go.mod
index f11853273..6c8c70012 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
 	github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect
 	github.com/Masterminds/sprig/v3 v3.0.0
 	github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0
+	github.com/avast/retry-go/v3 v3.1.1
 	github.com/bwmarrin/discordgo v0.23.0
 	github.com/bxcodec/faker/v3 v3.3.1
 	github.com/cenkalti/backoff v2.2.1+incompatible
@@ -70,7 +71,7 @@ require (
 	github.com/ory/kratos-client-go v0.6.3-alpha.1
 	github.com/ory/mail/v3 v3.0.0
 	github.com/ory/nosurf v1.2.4
-	github.com/ory/x v0.0.250
+	github.com/ory/x v0.0.256
 	github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
 	github.com/philhofer/fwd v1.1.1 // indirect
 	github.com/pkg/errors v0.9.1
diff --git a/go.sum b/go.sum
index 516203535..ec810fd5e 100644
--- a/go.sum
+++ b/go.sum
@@ -127,6 +127,8 @@ github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:o
 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
 github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
 github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/avast/retry-go/v3 v3.1.1 h1:49Scxf4v8PmiQ/nY0aY3p0hDueqSmc7++cBbtiDGu2g=
+github.com/avast/retry-go/v3 v3.1.1/go.mod h1:6cXRK369RpzFL3UQGqIUp9Q7GDrams+KsYWrfNA1/nQ=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@@ -382,6 +384,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-bindata/go-bindata v3.1.1+incompatible h1:tR4f0e4VTO7LK6B2YWyAoVEzG9ByG1wrXB4TL9+jiYg=
 github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
 github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=


Test patch:
diff --git a/cmd/identities/delete_test.go b/cmd/identities/delete_test.go
index 8103948d7..ee0791729 100644
--- a/cmd/identities/delete_test.go
+++ b/cmd/identities/delete_test.go
@@ -1,4 +1,4 @@
-package identities
+package identities_test
 
 import (
 	"context"
@@ -6,6 +6,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/ory/kratos/cmd/identities"
+
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
@@ -16,14 +18,15 @@ import (
 )
 
 func TestDeleteCmd(t *testing.T) {
-	reg := setup(t, DeleteCmd)
+	c := identities.NewDeleteCmd()
+	reg := setup(t, c)
 
 	t.Run("case=deletes successfully", func(t *testing.T) {
 		// create identity to delete
 		i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
 		require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
 
-		stdOut := execNoErr(t, DeleteCmd, i.ID.String())
+		stdOut := execNoErr(t, c, i.ID.String())
 
 		// expect ID and no error
 		assert.Equal(t, i.ID.String()+"\n", stdOut)
@@ -36,7 +39,7 @@ func TestDeleteCmd(t *testing.T) {
 	t.Run("case=deletes three identities", func(t *testing.T) {
 		is, ids := makeIdentities(t, reg, 3)
 
-		stdOut := execNoErr(t, DeleteCmd, ids...)
+		stdOut := execNoErr(t, c, ids...)
 
 		assert.Equal(t, strings.Join(ids, "\n")+"\n", stdOut)
 
@@ -47,7 +50,7 @@ func TestDeleteCmd(t *testing.T) {
 	})
 
 	t.Run("case=fails with unknown ID", func(t *testing.T) {
-		stdErr := execErr(t, DeleteCmd, x.NewUUID().String())
+		stdErr := execErr(t, c, x.NewUUID().String())
 
 		assert.Contains(t, stdErr, "404 Not Found", stdErr)
 	})
diff --git a/cmd/identities/get_test.go b/cmd/identities/get_test.go
index 2699d7e61..636951715 100644
--- a/cmd/identities/get_test.go
+++ b/cmd/identities/get_test.go
@@ -1,10 +1,12 @@
-package identities
+package identities_test
 
 import (
 	"context"
 	"encoding/json"
 	"testing"
 
+	"github.com/ory/kratos/cmd/identities"
+
 	"github.com/ory/x/assertx"
 
 	"github.com/ory/kratos/x"
@@ -17,13 +19,14 @@ import (
 )
 
 func TestGetCmd(t *testing.T) {
-	reg := setup(t, GetCmd)
+	c := identities.NewGetCmd()
+	reg := setup(t, c)
 
 	t.Run("case=gets a single identity", func(t *testing.T) {
 		i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
 		require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
 
-		stdOut := execNoErr(t, GetCmd, i.ID.String())
+		stdOut := execNoErr(t, c, i.ID.String())
 
 		ij, err := json.Marshal(i)
 		require.NoError(t, err)
@@ -34,7 +37,7 @@ func TestGetCmd(t *testing.T) {
 	t.Run("case=gets three identities", func(t *testing.T) {
 		is, ids := makeIdentities(t, reg, 3)
 
-		stdOut := execNoErr(t, GetCmd, ids...)
+		stdOut := execNoErr(t, c, ids...)
 
 		isj, err := json.Marshal(is)
 		require.NoError(t, err)
@@ -43,7 +46,7 @@ func TestGetCmd(t *testing.T) {
 	})
 
 	t.Run("case=fails with unknown ID", func(t *testing.T) {
-		stdErr := execErr(t, GetCmd, x.NewUUID().String())
+		stdErr := execErr(t, c, x.NewUUID().String())
 
 		assert.Contains(t, stdErr, "404 Not Found", stdErr)
 	})
diff --git a/cmd/identities/helpers_test.go b/cmd/identities/helpers_test.go
new file mode 100644
index 000000000..1cc390529
--- /dev/null
+++ b/cmd/identities/helpers_test.go
@@ -0,0 +1,73 @@
+package identities_test
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"testing"
+
+	"github.com/ory/x/cmdx"
+
+	"github.com/pkg/errors"
+
+	"github.com/ory/kratos/identity"
+
+	"github.com/spf13/cobra"
+	"github.com/stretchr/testify/require"
+
+	"github.com/ory/kratos/cmd/cliclient"
+	"github.com/ory/kratos/driver"
+	"github.com/ory/kratos/driver/config"
+	"github.com/ory/kratos/internal"
+	"github.com/ory/kratos/internal/testhelpers"
+)
+
+func setup(t *testing.T, cmd *cobra.Command) driver.Registry {
+	conf, reg := internal.NewFastRegistryWithMocks(t)
+	_, admin := testhelpers.NewKratosServerWithCSRF(t, reg)
+	conf.MustSet(config.ViperKeyDefaultIdentitySchemaURL, "file://./stubs/identity.schema.json")
+	// setup command
+	cliclient.RegisterClientFlags(cmd.Flags())
+	cmdx.RegisterFormatFlags(cmd.Flags())
+	require.NoError(t, cmd.Flags().Set(cliclient.FlagEndpoint, admin.URL))
+	require.NoError(t, cmd.Flags().Set(cmdx.FlagFormat, string(cmdx.FormatJSON)))
+	return reg
+}
+
+func exec(cmd *cobra.Command, stdIn io.Reader, args ...string) (string, string, error) {
+	stdOut, stdErr := &bytes.Buffer{}, &bytes.Buffer{}
+	cmd.SetErr(stdErr)
+	cmd.SetOut(stdOut)
+	cmd.SetIn(stdIn)
+	defer cmd.SetIn(nil)
+	if args == nil {
+		args = []string{}
+	}
+	cmd.SetArgs(args)
+	err := cmd.Execute()
+	return stdOut.String(), stdErr.String(), err
+}
+
+func execNoErr(t *testing.T, cmd *cobra.Command, args ...string) string {
+	stdOut, stdErr, err := exec(cmd, nil, args...)
+	require.NoError(t, err)
+	require.Len(t, stdErr, 0, stdOut)
+	return stdOut
+}
+
+func execErr(t *testing.T, cmd *cobra.Command, args ...string) string {
+	stdOut, stdErr, err := exec(cmd, nil, args...)
+	require.True(t, errors.Is(err, cmdx.ErrNoPrintButFail))
+	require.Len(t, stdOut, 0, stdErr)
+	return stdErr
+}
+
+func makeIdentities(t *testing.T, reg driver.Registry, n int) (is []*identity.Identity, ids []string) {
+	for j := 0; j < n; j++ {
+		i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
+		require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
+		is = append(is, i)
+		ids = append(ids, i.ID.String())
+	}
+	return
+}
diff --git a/cmd/identities/import_test.go b/cmd/identities/import_test.go
index c19bdf44c..e512c3cc0 100644
--- a/cmd/identities/import_test.go
+++ b/cmd/identities/import_test.go
@@ -1,4 +1,4 @@
-package identities
+package identities_test
 
 import (
 	"bytes"
@@ -8,6 +8,8 @@ import (
 	"io/ioutil"
 	"testing"
 
+	"github.com/ory/kratos/cmd/identities"
+
 	"github.com/gofrs/uuid"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -19,7 +21,8 @@ import (
 )
 
 func TestImportCmd(t *testing.T) {
-	reg := setup(t, ImportCmd)
+	c := identities.NewImportCmd()
+	reg := setup(t, c)
 
 	t.Run("case=imports a new identity from file", func(t *testing.T) {
 		i := kratos.CreateIdentity{
@@ -34,7 +37,7 @@ func TestImportCmd(t *testing.T) {
 		require.NoError(t, err)
 		require.NoError(t, f.Close())
 
-		stdOut := execNoErr(t, ImportCmd, f.Name())
+		stdOut := execNoErr(t, c, f.Name())
 
 		id, err := uuid.FromString(gjson.Get(stdOut, "id").String())
 		require.NoError(t, err)
@@ -61,7 +64,7 @@ func TestImportCmd(t *testing.T) {
 		require.NoError(t, err)
 		require.NoError(t, f.Close())
 
-		stdOut := execNoErr(t, ImportCmd, f.Name())
+		stdOut := execNoErr(t, c, f.Name())
 
 		id, err := uuid.FromString(gjson.Get(stdOut, "0.id").String())
 		require.NoError(t, err)
@@ -88,7 +91,7 @@ func TestImportCmd(t *testing.T) {
 		ij, err := json.Marshal(i)
 		require.NoError(t, err)
 
-		stdOut, stdErr, err := exec(ImportCmd, bytes.NewBuffer(ij))
+		stdOut, stdErr, err := exec(c, bytes.NewBuffer(ij))
 		require.NoError(t, err, "%s %s", stdOut, stdErr)
 
 		id, err := uuid.FromString(gjson.Get(stdOut, "0.id").String())
@@ -110,7 +113,7 @@ func TestImportCmd(t *testing.T) {
 		ij, err := json.Marshal(i)
 		require.NoError(t, err)
 
-		stdOut, stdErr, err := exec(ImportCmd, bytes.NewBuffer(ij))
+		stdOut, stdErr, err := exec(c, bytes.NewBuffer(ij))
 		require.NoError(t, err, "%s %s", stdOut, stdErr)
 
 		id, err := uuid.FromString(gjson.Get(stdOut, "id").String())
@@ -121,7 +124,7 @@ func TestImportCmd(t *testing.T) {
 
 	t.Run("case=fails to import invalid identity", func(t *testing.T) {
 		// validation is further tested with the validate command
-		stdOut, stdErr, err := exec(ImportCmd, bytes.NewBufferString("{}"))
+		stdOut, stdErr, err := exec(c, bytes.NewBufferString("{}"))
 		assert.True(t, errors.Is(err, cmdx.ErrNoPrintButFail))
 		assert.Contains(t, stdErr, "STD_IN[0]: not valid")
 		assert.Len(t, stdOut, 0)
diff --git a/cmd/identities/list_test.go b/cmd/identities/list_test.go
index c1bffddd8..86a81782f 100644
--- a/cmd/identities/list_test.go
+++ b/cmd/identities/list_test.go
@@ -1,10 +1,12 @@
-package identities
+package identities_test
 
 import (
 	"context"
 	"strings"
 	"testing"
 
+	"github.com/ory/kratos/cmd/identities"
+
 	"github.com/ory/x/cmdx"
 
 	"github.com/stretchr/testify/assert"
@@ -14,8 +16,9 @@ import (
 )
 
 func TestListCmd(t *testing.T) {
-	reg := setup(t, ListCmd)
-	require.NoError(t, ListCmd.Flags().Set(cmdx.FlagQuiet, "true"))
+	c := identities.NewListCmd()
+	reg := setup(t, c)
+	require.NoError(t, c.Flags().Set(cmdx.FlagQuiet, "true"))
 
 	var deleteIdentities = func(t *testing.T, is []*identity.Identity) {
 		for _, i := range is {
@@ -27,7 +30,7 @@ func TestListCmd(t *testing.T) {
 		is, ids := makeIdentities(t, reg, 5)
 		defer deleteIdentities(t, is)
 
-		stdOut := execNoErr(t, ListCmd)
+		stdOut := execNoErr(t, c)
 
 		for _, i := range ids {
 			assert.Contains(t, stdOut, i)
@@ -38,8 +41,8 @@ func TestListCmd(t *testing.T) {
 		is, ids := makeIdentities(t, reg, 6)
 		defer deleteIdentities(t, is)
 
-		stdoutP1 := execNoErr(t, ListCmd, "1", "3")
-		stdoutP2 := execNoErr(t, ListCmd, "2", "3")
+		stdoutP1 := execNoErr(t, c, "1", "3")
+		stdoutP2 := execNoErr(t, c, "2", "3")
 
 		for _, id := range ids {
 			// exactly one of page 1 and 2 should contain the id
diff --git a/cmd/identities/validate_test.go b/cmd/identities/validate_test.go
index a36926a7c..02a301519 100644
--- a/cmd/identities/validate_test.go
+++ b/cmd/identities/validate_test.go
@@ -1,4 +1,4 @@
-package identities
+package identities_test
 
 import (
 	"bytes"
@@ -7,6 +7,8 @@ import (
 	"net/http"
 	"testing"
 
+	"github.com/ory/kratos/cmd/identities"
+
 	"github.com/spf13/cobra"
 	"github.com/stretchr/testify/assert"
 )
@@ -20,7 +22,7 @@ func TestValidateIdentity(t *testing.T) {
 		return cmd, out, err
 	}
 
-	var testSchemaGetter = func(resp map[string]interface{}) schemaGetter {
+	var testSchemaGetter = func(resp map[string]interface{}) identities.SchemaGetter {
 		return func(_ context.Context, _ string) (map[string]interface{}, *http.Response, error) {
 			return resp, nil, nil
 		}
@@ -63,7 +65,7 @@ func TestValidateIdentity(t *testing.T) {
 			if tc.identitySchema == nil {
 				tc.identitySchema = map[string]interface{}{}
 			}
-			err := validateIdentity(cmd, "test identity", tc.payload, testSchemaGetter(tc.identitySchema))
+			err := identities.ValidateIdentity(cmd, "test identity", tc.payload, testSchemaGetter(tc.identitySchema))
 			assert.Error(t, err, stdOut.String(), stdErr.String())
 			assert.Len(t, stdOut.String(), 0, stdErr.String())
 			assert.Contains(t, stdErr.String(), "required", stdOut.String())
diff --git a/cmd/serve/root_test.go b/cmd/serve/root_test.go
new file mode 100644
index 000000000..1ceac19e4
--- /dev/null
+++ b/cmd/serve/root_test.go
@@ -0,0 +1,11 @@
+package serve_test
+
+import (
+	"testing"
+
+	"github.com/ory/kratos/internal/testhelpers"
+)
+
+func TestServe(t *testing.T) {
+	_, _ = testhelpers.StartE2EServer(t, "../../contrib/quickstart/kratos/email-password/kratos.yml")
+}
diff --git a/examples/go/init/login/main_test.go b/examples/go/init/login/main_test.go
index b979d161f..5ed4b2685 100644
--- a/examples/go/init/login/main_test.go
+++ b/examples/go/init/login/main_test.go
@@ -3,13 +3,15 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestLogin(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 
 	flow := initLogin()
diff --git a/examples/go/init/recovery/main_test.go b/examples/go/init/recovery/main_test.go
index 828e84fed..499693561 100644
--- a/examples/go/init/recovery/main_test.go
+++ b/examples/go/init/recovery/main_test.go
@@ -3,13 +3,15 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestRecovery(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 
 	flow := initRecovery()
diff --git a/examples/go/init/registration/main_test.go b/examples/go/init/registration/main_test.go
index 301379b8a..877d70754 100644
--- a/examples/go/init/registration/main_test.go
+++ b/examples/go/init/registration/main_test.go
@@ -3,13 +3,15 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestRegistration(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 
 	flow := initRegistration()
diff --git a/examples/go/init/settings/main_test.go b/examples/go/init/settings/main_test.go
index 91c6de117..2628bd617 100644
--- a/examples/go/init/settings/main_test.go
+++ b/examples/go/init/settings/main_test.go
@@ -3,13 +3,15 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestSettings(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 
 	flow := initSettings()
diff --git a/examples/go/init/verification/main_test.go b/examples/go/init/verification/main_test.go
index def9a39a5..d5d00acc9 100644
--- a/examples/go/init/verification/main_test.go
+++ b/examples/go/init/verification/main_test.go
@@ -3,13 +3,15 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestVerification(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 
 	flow := initVerification()
diff --git a/examples/go/pkg/tests.go b/examples/go/pkg/tests.go
deleted file mode 100644
index a78d2e77d..000000000
--- a/examples/go/pkg/tests.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package pkg
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/phayes/freeport"
-	"github.com/spf13/cobra"
-	"github.com/stretchr/testify/require"
-
-	"github.com/ory/kratos/cmd"
-	"github.com/ory/x/cmdx"
-	"github.com/ory/x/configx"
-)
-
-func NewKratosServer(t testing.TB) (publicURL, adminURL string) {
-	dsn := "sqlite://file::memory:?_fk=true&cache=shared"
-
-	publicPort, err := freeport.GetFreePort()
-	require.NoError(t, err)
-
-	adminPort, err := freeport.GetFreePort()
-	require.NoError(t, err)
-
-	ctx := configx.ContextWithConfigOptions(context.Background(),
-		configx.WithValue("dsn", dsn),
-		configx.WithValue("dev", true),
-		configx.WithValue("log.level", "trace"),
-		configx.WithValue("serve.public.port", publicPort),
-		configx.WithValue("serve.admin.port", adminPort),
-	)
-	ctx, cancel := context.WithCancel(ctx)
-	t.Cleanup(cancel)
-
-	executor := &cmdx.CommandExecuter{
-		New: func() *cobra.Command {
-			return cmd.RootCmd
-		},
-		Ctx: ctx,
-	}
-	_ = executor.ExecNoErr(t, "")
-
-	go func() {
-		t.Log("Starting server...")
-		_ = executor.ExecNoErr(t, "serve", "--config", "../../pkg/stub/kratos.yaml")
-	}()
-
-	publicURL = fmt.Sprintf("http://127.0.0.1:%d", publicPort)
-	adminURL = fmt.Sprintf("http://127.0.0.1:%d", adminPort)
-
-	startedAt := time.Now()
-	for {
-		_, err := http.Get(fmt.Sprintf("%s/health/alive", publicURL))
-		if err == nil {
-			break
-		}
-
-		time.Sleep(50 * time.Millisecond)
-		if time.Now().Sub(startedAt).Seconds() > 5 {
-			t.Fatalf("Unable to reach server.")
-		}
-	}
-
-	return
-}
diff --git a/examples/go/submit/logout/main_test.go b/examples/go/submit/logout/main_test.go
index 6d42cd74f..dced37e4f 100644
--- a/examples/go/submit/logout/main_test.go
+++ b/examples/go/submit/logout/main_test.go
@@ -3,11 +3,13 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 )
 
 func TestLogout(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 
 	performLogout()
diff --git a/examples/go/submit/registration/main_test.go b/examples/go/submit/registration/main_test.go
index 972eba603..f9c19012e 100644
--- a/examples/go/submit/registration/main_test.go
+++ b/examples/go/submit/registration/main_test.go
@@ -3,11 +3,13 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 )
 
 func TestRegistration(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 	performRegistration()
 }
diff --git a/examples/go/submit/verification/main_test.go b/examples/go/submit/verification/main_test.go
index 0e0db2dda..d4045b670 100644
--- a/examples/go/submit/verification/main_test.go
+++ b/examples/go/submit/verification/main_test.go
@@ -3,6 +3,8 @@ package main
 import (
 	"testing"
 
+	"github.com/ory/kratos/internal/testhelpers"
+
 	"github.com/ory/kratos/examples/go/pkg"
 
 	"github.com/google/uuid"
@@ -11,7 +13,7 @@ import (
 )
 
 func TestVerification(t *testing.T) {
-	publicURL, _ := pkg.NewKratosServer(t)
+	publicURL, _ := testhelpers.StartE2EServer(t, "../../pkg/stub/kratos.yaml")
 	client = pkg.NewSDKForSelfHosted(publicURL)
 
 	flow := performVerification("dev+" + uuid.New().String() + "@ory.sh")
diff --git a/internal/testhelpers/e2e_server.go b/internal/testhelpers/e2e_server.go
new file mode 100644
index 000000000..d61a0e831
--- /dev/null
+++ b/internal/testhelpers/e2e_server.go
@@ -0,0 +1,68 @@
+package testhelpers
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/avast/retry-go/v3"
+	"github.com/phayes/freeport"
+	"github.com/spf13/cobra"
+	"github.com/stretchr/testify/require"
+
+	"github.com/ory/kratos/cmd"
+	"github.com/ory/kratos/x"
+	"github.com/ory/x/cmdx"
+	"github.com/ory/x/configx"
+)
+
+func StartE2EServer(t *testing.T, configFile string) (publicUrl, adminUrl string) {
+	adminPort, err := freeport.GetFreePort()
+	require.NoError(t, err)
+
+	publicPort, err := freeport.GetFreePort()
+	require.NoError(t, err)
+
+	ctx := configx.ContextWithConfigOptions(context.Background(),
+		configx.WithValue("dsn", "memory"),
+		configx.WithValue("dev", true),
+		configx.WithValue("log.level", "trace"),
+		configx.WithValue("log.leak_sensitive_values", true),
+		configx.WithValue("serve.public.port", publicPort),
+		configx.WithValue("serve.admin.port", adminPort),
+	)
+
+	//nolint:staticcheck
+	ctx = context.WithValue(ctx, "dsn", "memory")
+	ctx, cancel := context.WithCancel(ctx)
+	t.Cleanup(cancel)
+
+	executor := &cmdx.CommandExecuter{New: func() *cobra.Command {
+		return cmd.NewRootCmd()
+	}, Ctx: ctx}
+
+	go func() {
+		t.Log("Starting server...")
+		_ = executor.ExecNoErr(t, "serve", "--config", configFile, "--watch-courier")
+	}()
+
+	publicUrl = fmt.Sprintf("http://127.0.0.1:%d", publicPort)
+	adminUrl = fmt.Sprintf("http://127.0.0.1:%d", adminPort)
+
+	require.NoError(t, retry.Do(func() error {
+		res, err := http.Get(publicUrl + "/health/alive")
+		if err != nil {
+			return err
+		}
+		defer res.Body.Close()
+		body := x.MustReadAll(res.Body)
+		if res.StatusCode != http.StatusOK {
+			t.Logf("%s", body)
+			return fmt.Errorf("expected status code 200 but got: %d", res.StatusCode)
+		}
+		return nil
+	}), err)
+
+	return
+}