From 002637e128e325867b9ed853f460db18405525d3 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Thu, 24 Mar 2022 09:50:02 +0100 Subject: [PATCH] fix: add ability to render to Ory Kratos config YAML --- cloudx/cmd_get.go | 1 - cloudx/cmd_get_project.go | 10 ++++--- cloudx/cmd_get_project_test.go | 10 +++++++ cloudx/cmd_patch_project.go | 33 ++++++++++++++---------- cloudx/cmd_patch_project_test.go | 10 +++++++ cloudx/cmd_update_project.go | 34 ++++++++++++------------ cloudx/cmd_update_project_test.go | 10 +++++++ cloudx/handler.go | 13 +++++----- cloudx/print.go | 43 +++++++++++++++++++++++++++++++ 9 files changed, 123 insertions(+), 41 deletions(-) diff --git a/cloudx/cmd_get.go b/cloudx/cmd_get.go index 3f87d0c1..aa1372bf 100644 --- a/cloudx/cmd_get.go +++ b/cloudx/cmd_get.go @@ -17,6 +17,5 @@ func NewGetCmd() *cobra.Command { RegisterConfigFlag(cmd.PersistentFlags()) RegisterYesFlag(cmd.PersistentFlags()) cmdx.RegisterNoiseFlags(cmd.PersistentFlags()) - cmdx.RegisterJSONFormatFlags(cmd.PersistentFlags()) return cmd } diff --git a/cloudx/cmd_get_project.go b/cloudx/cmd_get_project.go index 13f8f298..78c111e5 100644 --- a/cloudx/cmd_get_project.go +++ b/cloudx/cmd_get_project.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/spf13/cobra" - - "github.com/ory/x/cmdx" ) func NewGetProjectCmd() *cobra.Command { @@ -13,6 +11,10 @@ func NewGetProjectCmd() *cobra.Command { Use: "project id", Args: cobra.ExactArgs(1), Short: fmt.Sprintf("Get an Ory Cloud project"), + Example: `ory get project ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 +ory get project ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 --format json +ory get project ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 --format kratos-config > kratos-config.yml`, + Long: `If you wish to generate a configuration for self-hosting Ory Kratos, use ` + "`--format kratos-config`" + `.`, RunE: func(cmd *cobra.Command, args []string) error { h, err := NewSnakeCharmer(cmd) if err != nil { @@ -24,10 +26,10 @@ func NewGetProjectCmd() *cobra.Command { return PrintOpenAPIError(cmd, err) } - cmdx.PrintRow(cmd, (*outputProject)(project)) - return nil + return PrintExtendedFormat(cmd, project) }, } + RegisterExtendedOutput(cmd.Flags()) return cmd } diff --git a/cloudx/cmd_get_project_test.go b/cloudx/cmd_get_project_test.go index 131df115..c4e6ebf9 100644 --- a/cloudx/cmd_get_project_test.go +++ b/cloudx/cmd_get_project_test.go @@ -5,6 +5,8 @@ import ( "fmt" "testing" + "github.com/ghodss/yaml" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -41,4 +43,12 @@ func TestGetProject(t *testing.T) { require.NoError(t, err) assert.Contains(t, project, gjson.Parse(stdout).Get("id").String()) }) + + t.Run("is able to get project as a kratos config", func(t *testing.T) { + stdout, _, err := cmd.ExecDebug(t, nil, "get", "project", project, "--format", "kratos-config") + require.NoError(t, err) + actual, err := yaml.YAMLToJSON([]byte(stdout)) + require.NoError(t, err) + assert.Equal(t, "/ui/error", gjson.GetBytes(actual, "selfservice.flows.error.ui_url").String()) + }) } diff --git a/cloudx/cmd_patch_project.go b/cloudx/cmd_patch_project.go index fe1b3171..29dc9e0f 100644 --- a/cloudx/cmd_patch_project.go +++ b/cloudx/cmd_patch_project.go @@ -1,14 +1,12 @@ package cloudx import ( - "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/ory/x/flagx" - "github.com/ory/x/cmdx" + + "github.com/ory/x/flagx" ) func NewProjectsPatchCmd() *cobra.Command { @@ -20,7 +18,21 @@ func NewProjectsPatchCmd() *cobra.Command { --replace '/name="My new project name"' \ --add '/services/identity/config/courier/smtp={"from_name":"My new email name"}' \ --replace '/services/identity/config/selfservice/methods/password/enabled=false' \ - --delete '/services/identity/config/selfservice/methods/totp/enabled'`, + --delete '/services/identity/config/selfservice/methods/totp/enabled' + +ory patch project ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 \ + --replace '/name="My new project name"' \ + --delete '/services/identity/config/selfservice/methods/totp/enabled' + --format kratos-config > my-config.yaml`, + Long: `Use this command to patch your current Ory Cloud Project's service configuration. Only values +specified in the patch will be overwritten. To replace the config use the ` + "`update`" + ` command instead. + +The format of the patch is a JSON-Patch document. For more details please check: + + https://www.ory.sh/docs/reference/api#operation/patchProject + https://jsonpatch.com + +If you wish to generate a configuration for self-hosting Ory Kratos, use ` + "`--format kratos-config`" + `.`, RunE: func(cmd *cobra.Command, args []string) (err error) { h, err := NewSnakeCharmer(cmd) if err != nil { @@ -46,13 +58,7 @@ func NewProjectsPatchCmd() *cobra.Command { return PrintOpenAPIError(cmd, err) } - cmdx.PrintRow(cmd, (*outputProject)(&p.Project)) - for _, warning := range p.Warnings { - _, _ = fmt.Fprintf(h.verboseErrWriter, "WARNING: %s\n", *warning.Message) - } - - _, _ = fmt.Fprintln(h.verboseErrWriter, "Project updated successfully!") - return nil + return h.PrintUpdateProject(cmd, p) }, } @@ -61,6 +67,7 @@ func NewProjectsPatchCmd() *cobra.Command { cmd.Flags().StringArray("add", nil, "Add a specific key to the configuration") cmd.Flags().StringArray("remove", nil, "Remove a specific key from the configuration") RegisterYesFlag(cmd.Flags()) - cmdx.RegisterFormatFlags(cmd.Flags()) + cmdx.RegisterNoiseFlags(cmd.Flags()) + RegisterExtendedOutput(cmd.Flags()) return cmd } diff --git a/cloudx/cmd_patch_project_test.go b/cloudx/cmd_patch_project_test.go index 8b6a0afc..708586dc 100644 --- a/cloudx/cmd_patch_project_test.go +++ b/cloudx/cmd_patch_project_test.go @@ -3,6 +3,8 @@ package cloudx import ( "testing" + "github.com/ghodss/yaml" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -38,6 +40,14 @@ func TestPatchProject(t *testing.T) { assert.Equal(t, "https://example.org/error-ui", gjson.Get(stdout, "services.identity.config.selfservice.flows.error.ui_url").String()) }) + t.Run("is able to add a key with raw json and output it as a kratos config", func(t *testing.T) { + stdout, _, err := cmd.ExecDebug(t, nil, "patch", "project", project, "--format", "kratos-config", "--replace", `/services/identity/config/selfservice/flows/error={"ui_url":"https://example.org/kratos-ui"}`) + require.NoError(t, err) + actual, err := yaml.YAMLToJSON([]byte(stdout)) + require.NoError(t, err) + assert.Equal(t, "https://example.org/kratos-ui", gjson.GetBytes(actual, "selfservice.flows.error.ui_url").String()) + }) + t.Run("is able to remove a key", func(t *testing.T) { stdout, _, err := cmd.ExecDebug(t, nil, "patch", "project", project, "--format", "json", "--remove", `/services/identity/config/selfservice/methods/password/enabled`) require.NoError(t, err) diff --git a/cloudx/cmd_update_project.go b/cloudx/cmd_update_project.go index 14012d0e..ec72cfb5 100644 --- a/cloudx/cmd_update_project.go +++ b/cloudx/cmd_update_project.go @@ -1,12 +1,11 @@ package cloudx import ( - "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/ory/x/cmdx" + "github.com/ory/x/flagx" ) @@ -16,20 +15,26 @@ func NewProjectsUpdateCmd() *cobra.Command { Args: cobra.ExactArgs(1), Short: "Update Ory Cloud Project Service Configuration", Example: `ory update project ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 \ ---name \"my updated name\" \ ---file /path/to/config.json \ ---file /path/to/config.yml \ ---file https://example.org/config.yaml \ ---file base64://`, + --name \"my updated name\" \ + --file /path/to/config.json \ + --file /path/to/config.yml \ + --file https://example.org/config.yaml \ + --file base64:// + +ory update project ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 \ + --name \"my updated name\" \ + --file /path/to/config.json \ + --format kratos-config > my-config.yaml`, Long: `Use this command to replace your current Ory Cloud Project's service configuration. All values -will be overwritten. To update individual files use the ` + "`patch`" + ` command instead. +will be overwritten. To update individual settings use the ` + "`patch`" + ` command instead. If the ` + "`--name`" + ` flag is not set, the project's name will not be changed. The configuration file format can be found at: https://www.ory.sh/docs/reference/api#operation/updateProject -`, + +If you wish to generate a configuration for self-hosting Ory Kratos, use ` + "`--format kratos-config`" + `.`, RunE: func(cmd *cobra.Command, args []string) (err error) { h, err := NewSnakeCharmer(cmd) if err != nil { @@ -52,19 +57,14 @@ The configuration file format can be found at: return PrintOpenAPIError(cmd, err) } - cmdx.PrintRow(cmd, (*outputProject)(&p.Project)) - for _, warning := range p.Warnings { - _, _ = fmt.Fprintf(h.verboseErrWriter, "WARNING: %s\n", *warning.Message) - } - - _, _ = fmt.Fprintln(h.verboseErrWriter, "Project updated successfully!") - return nil + return h.PrintUpdateProject(cmd, p) }, } cmd.Flags().StringP("name", "n", "", "The name of the project, required when quiet mode is used") cmd.Flags().StringSliceP("file", "f", nil, "Configuration file(s) (file://config.json, https://example.org/config.yaml, ...) to update the project") - cmdx.RegisterFormatFlags(cmd.Flags()) RegisterYesFlag(cmd.Flags()) + cmdx.RegisterNoiseFlags(cmd.Flags()) + RegisterExtendedOutput(cmd.Flags()) return cmd } diff --git a/cloudx/cmd_update_project_test.go b/cloudx/cmd_update_project_test.go index 7ff13cb7..c1547a28 100644 --- a/cloudx/cmd_update_project_test.go +++ b/cloudx/cmd_update_project_test.go @@ -6,6 +6,8 @@ import ( "encoding/json" "testing" + "github.com/ghodss/yaml" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -81,4 +83,12 @@ func TestUpdateProject(t *testing.T) { _, _, err := cmd.ExecDebug(t, &r, "update", "project", project, "--format", "json", "--file", "./fixtures/update/json/config.json") require.NoError(t, err) }) + + t.Run("is able to update a project and get it as a kratos config", func(t *testing.T) { + stdout, _, err := cmd.ExecDebug(t, nil, "update", "project", project, "--format", "kratos-config", "--file", "./fixtures/update/json/config.json") + require.NoError(t, err) + actual, err := yaml.YAMLToJSON([]byte(stdout)) + require.NoError(t, err) + assert.Equal(t, "/ui/error", gjson.GetBytes(actual, "selfservice.flows.error.ui_url").String()) + }) } diff --git a/cloudx/handler.go b/cloudx/handler.go index a7699ae4..0c4f5254 100644 --- a/cloudx/handler.go +++ b/cloudx/handler.go @@ -35,12 +35,13 @@ import ( ) const ( - fileName = ".ory-cloud.json" - configFlag = "config" - osEnvVar = "ORY_CLOUD_CONFIG_PATH" - cloudURL = "ORY_CLOUD_URL" - version = "v0alpha0" - yesFlag = "yes" + fileName = ".ory-cloud.json" + configFlag = "config" + osEnvVar = "ORY_CLOUD_CONFIG_PATH" + cloudURL = "ORY_CLOUD_URL" + version = "v0alpha0" + yesFlag = "yes" + FormatKratosConfig = "kratos-config" ) func RegisterConfigFlag(f *pflag.FlagSet) { diff --git a/cloudx/print.go b/cloudx/print.go index e239978e..d065b256 100644 --- a/cloudx/print.go +++ b/cloudx/print.go @@ -4,10 +4,15 @@ import ( "encoding/json" "fmt" + "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/tidwall/gjson" + "github.com/ory/client-go" + "github.com/ory/x/flagx" + "github.com/ory/x/cmdx" ) @@ -46,3 +51,41 @@ func PrintOpenAPIError(cmd *cobra.Command, err error) error { return err } + +func RegisterExtendedOutput(flags *pflag.FlagSet) { + flags.String(cmdx.FlagFormat, string(cmdx.FormatDefault), fmt.Sprintf("Set the output format. One of %s, %s, %s, and %s.", cmdx.FormatDefault, cmdx.FormatJSON, cmdx.FormatJSONPretty, FormatKratosConfig)) +} + +func PrintExtendedFormat(cmd *cobra.Command, project *client.Project) error { + if flagx.MustGetString(cmd, cmdx.FlagFormat) != FormatKratosConfig { + cmdx.PrintRow(cmd, (*outputProject)(project)) + return nil + } + + out, err := yaml.Marshal(project.Services.Identity.Config) + if err != nil { + return errors.WithStack(err) + } + if _, err := fmt.Fprintf(cmd.OutOrStdout(), "%s\n", out); err != nil { + return err + } + return nil +} + +func (h *SnakeCharmer) PrintUpdateProject(cmd *cobra.Command, p *client.SuccessfulProjectUpdate) error { + if err := PrintExtendedFormat(cmd, &p.Project); err != nil { + return err + } + + if len(p.Warnings) > 0 { + _, _ = fmt.Fprintln(h.verboseErrWriter) + _, _ = fmt.Fprintln(h.verboseErrWriter, "Warnings were found.") + for _, warning := range p.Warnings { + _, _ = fmt.Fprintf(h.verboseErrWriter, "- %s\n", *warning.Message) + } + _, _ = fmt.Fprintln(h.verboseErrWriter, "It is save to ignore these warnings unless your intention was to set these keys.") + } + + _, _ = fmt.Fprintf(h.verboseErrWriter, "\nProject updated successfully!\n") + return nil +}