diff --git a/.gitignore b/.gitignore index 7ade2c2f..5761a099 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ unpacked_bin/ .idea cypress/screenshots cypress/videos +cli diff --git a/cmd/cloudx/client/.snapshots/TestCommandHelper-func=UpdateProject-is_able_to_update_a_project.json b/cmd/cloudx/client/.snapshots/TestCommandHelper-func=UpdateProject-is_able_to_update_a_project.json index c04caae9..4c008790 100644 --- a/cmd/cloudx/client/.snapshots/TestCommandHelper-func=UpdateProject-is_able_to_update_a_project.json +++ b/cmd/cloudx/client/.snapshots/TestCommandHelper-func=UpdateProject-is_able_to_update_a_project.json @@ -47,8 +47,7 @@ "url": "base64://ewogICIkaWQiOiAiaHR0cHM6Ly9zY2hlbWFzLm9yeS5zaC9wcmVzZXRzL2tyYXRvcy9pZGVudGl0eS5lbWFpbC5zY2hlbWEuanNvbiIsCiAgIiRzY2hlbWEiOiAiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIiwKICAidGl0bGUiOiAiUGVyc29uIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJmb3JtYXQiOiAiZW1haWwiLAogICAgICAgICAgInRpdGxlIjogIkUtTWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIndlYmF1dGhuIjogewogICAgICAgICAgICAgICAgImlkZW50aWZpZXIiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAidG90cCI6IHsKICAgICAgICAgICAgICAgICJhY2NvdW50X25hbWUiOiB0cnVlCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICAicmVjb3ZlcnkiOiB7CiAgICAgICAgICAgICAgInZpYSI6ICJlbWFpbCIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZlcmlmaWNhdGlvbiI6IHsKICAgICAgICAgICAgICAidmlhIjogImVtYWlsIgogICAgICAgICAgICB9CiAgICAgICAgICB9LAogICAgICAgICAgIm1heExlbmd0aCI6IDMyMAogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJlbWFpbCIKICAgICAgXSwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UKICAgIH0KICB9Cn0K" }, { - "id": "beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782", - "url": "https://storage.googleapis.com/bac-gcs-production/beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782.json" + "id": "beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782" }, { "id": "preset://username", diff --git a/cmd/cloudx/client/client.go b/cmd/cloudx/client/client.go index 542fb6a6..dc3816fa 100644 --- a/cmd/cloudx/client/client.go +++ b/cmd/cloudx/client/client.go @@ -30,6 +30,17 @@ func RegisterProjectFlag(f *flag.FlagSet) { f.String(projectFlag, "", "The project to use") } +// ProjectID returns the ID the user set with the `--project` flag, or prints a warning and returns an error +// if none was set. +func ProjectID(cmd *cobra.Command) (uuid.UUID, error) { + project := uuid.FromStringOrNil(flagx.MustGetString(cmd, projectFlag)) + if project == uuid.Nil { + _, _ = fmt.Fprintf(os.Stderr, "No project selected! Please use the flag --%s to specify one.\n", projectFlag) + return uuid.Nil, cmdx.FailSilently(cmd) + } + return project, nil +} + func Client(cmd *cobra.Command) (*retryablehttp.Client, *AuthContext, *cloud.Project, error) { sc, err := NewCommandHelper(cmd) if err != nil { @@ -42,9 +53,8 @@ func Client(cmd *cobra.Command) (*retryablehttp.Client, *AuthContext, *cloud.Pro return nil, nil, nil, err } - project := uuid.FromStringOrNil(flagx.MustGetString(cmd, projectFlag)) - if project == uuid.Nil { - _, _ = fmt.Fprintf(os.Stderr, "No project selected! Please use the flag --%s to specify one.\n", projectFlag) + project, err := ProjectID(cmd) + if err != nil { return nil, nil, nil, cmdx.FailSilently(cmd) } diff --git a/cmd/cloudx/client/handler_test.go b/cmd/cloudx/client/handler_test.go index 8fc79a59..2efb0890 100644 --- a/cmd/cloudx/client/handler_test.go +++ b/cmd/cloudx/client/handler_test.go @@ -130,6 +130,7 @@ func TestCommandHelper(t *testing.T) { "project.services.oauth2.config.urls.self", "project.services.oauth2.config.serve.cookies.domain", "project.services.oauth2.config.serve.cookies.names", + "project.services.identity.config.identity.schemas.1.url", // bucket changes locally vs staging )) }) diff --git a/cmd/cloudx/patch.go b/cmd/cloudx/patch.go index c25d4f3f..6f58443c 100644 --- a/cmd/cloudx/patch.go +++ b/cmd/cloudx/patch.go @@ -18,6 +18,7 @@ func NewPatchCmd() *cobra.Command { project.NewPatchKratosConfigCmd(), project.NewPatchKetoConfigCmd(), project.NewPatchOAuth2ConfigCmd(), + project.NewUpdateNamespaceConfigCmd(), ) return cmd } diff --git a/cmd/cloudx/project/.snapshots/TestUpdateProject-target=identity-config-is_able_to_update_a_project.json b/cmd/cloudx/project/.snapshots/TestUpdateProject-target=identity-config-is_able_to_update_a_project.json index c74758f0..ba5b30ae 100644 --- a/cmd/cloudx/project/.snapshots/TestUpdateProject-target=identity-config-is_able_to_update_a_project.json +++ b/cmd/cloudx/project/.snapshots/TestUpdateProject-target=identity-config-is_able_to_update_a_project.json @@ -41,8 +41,7 @@ "url": "base64://ewogICIkaWQiOiAiaHR0cHM6Ly9zY2hlbWFzLm9yeS5zaC9wcmVzZXRzL2tyYXRvcy9pZGVudGl0eS5lbWFpbC5zY2hlbWEuanNvbiIsCiAgIiRzY2hlbWEiOiAiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIiwKICAidGl0bGUiOiAiUGVyc29uIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJmb3JtYXQiOiAiZW1haWwiLAogICAgICAgICAgInRpdGxlIjogIkUtTWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIndlYmF1dGhuIjogewogICAgICAgICAgICAgICAgImlkZW50aWZpZXIiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAidG90cCI6IHsKICAgICAgICAgICAgICAgICJhY2NvdW50X25hbWUiOiB0cnVlCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICAicmVjb3ZlcnkiOiB7CiAgICAgICAgICAgICAgInZpYSI6ICJlbWFpbCIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZlcmlmaWNhdGlvbiI6IHsKICAgICAgICAgICAgICAidmlhIjogImVtYWlsIgogICAgICAgICAgICB9CiAgICAgICAgICB9LAogICAgICAgICAgIm1heExlbmd0aCI6IDMyMAogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJlbWFpbCIKICAgICAgXSwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UKICAgIH0KICB9Cn0K" }, { - "id": "beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782", - "url": "https://storage.googleapis.com/bac-gcs-staging/beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782.json" + "id": "beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782" }, { "id": "preset://username", diff --git a/cmd/cloudx/project/.snapshots/TestUpdateProject-target=project-is_able_to_update_a_project.json b/cmd/cloudx/project/.snapshots/TestUpdateProject-target=project-is_able_to_update_a_project.json index ccd31e20..17702308 100644 --- a/cmd/cloudx/project/.snapshots/TestUpdateProject-target=project-is_able_to_update_a_project.json +++ b/cmd/cloudx/project/.snapshots/TestUpdateProject-target=project-is_able_to_update_a_project.json @@ -46,8 +46,7 @@ "url": "base64://ewogICIkaWQiOiAiaHR0cHM6Ly9zY2hlbWFzLm9yeS5zaC9wcmVzZXRzL2tyYXRvcy9pZGVudGl0eS5lbWFpbC5zY2hlbWEuanNvbiIsCiAgIiRzY2hlbWEiOiAiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIiwKICAidGl0bGUiOiAiUGVyc29uIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJmb3JtYXQiOiAiZW1haWwiLAogICAgICAgICAgInRpdGxlIjogIkUtTWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIndlYmF1dGhuIjogewogICAgICAgICAgICAgICAgImlkZW50aWZpZXIiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAidG90cCI6IHsKICAgICAgICAgICAgICAgICJhY2NvdW50X25hbWUiOiB0cnVlCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICAicmVjb3ZlcnkiOiB7CiAgICAgICAgICAgICAgInZpYSI6ICJlbWFpbCIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgInZlcmlmaWNhdGlvbiI6IHsKICAgICAgICAgICAgICAidmlhIjogImVtYWlsIgogICAgICAgICAgICB9CiAgICAgICAgICB9LAogICAgICAgICAgIm1heExlbmd0aCI6IDMyMAogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJlbWFpbCIKICAgICAgXSwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UKICAgIH0KICB9Cn0K" }, { - "id": "beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782", - "url": "https://storage.googleapis.com/bac-gcs-staging/beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782.json" + "id": "beb7e08859084ec71bc073694fff7c71eb0eb82593ca11b29068c1e02ad385c4fb8451cb3b58d8c9385ed955f31ab51b6d94e187ffea8d9ba817c12782ef5782" }, { "id": "preset://username", diff --git a/cmd/cloudx/project/patch_permission_config.go b/cmd/cloudx/project/patch_permission_config.go index 7591ed35..28a4c5d0 100644 --- a/cmd/cloudx/project/patch_permission_config.go +++ b/cmd/cloudx/project/patch_permission_config.go @@ -39,7 +39,7 @@ Compared to the ` + "`patch project`" + ` command, this command only updates the and also only returns the permission service configuration as a result. This command is useful when you want to import an Ory Keto config as well, for example. This allows for shorter paths when specifying the flags - ory patch identity-config ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 \ + ory patch permission-config ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 \ --replace '/limit/max_read_depth=5' when compared to the ` + "`patch project`" + ` command: diff --git a/cmd/cloudx/project/update_namespace_config.go b/cmd/cloudx/project/update_namespace_config.go new file mode 100644 index 00000000..e7b20b59 --- /dev/null +++ b/cmd/cloudx/project/update_namespace_config.go @@ -0,0 +1,67 @@ +package project + +import ( + "encoding/base64" + "fmt" + + "github.com/spf13/cobra" + + "github.com/ory/cli/cmd/cloudx/client" + "github.com/ory/x/cmdx" + "github.com/ory/x/flagx" + "github.com/ory/x/osx" +) + +func NewUpdateNamespaceConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "opl ", + Aliases: []string{ + "namespaces-config", + }, + Args: cobra.NoArgs, + Short: "Update the Ory Permission Language file in Ory Cloud", + Example: `$ {{ .CommandPath }} ecaaa3cb-0730-4ee8-a6df-9553cdfeef89 \ + --file /path/to/namespace_config.ts + +class Example implements Namespace {} +`, + Long: "Update the Ory Permission Language file in Ory Cloud. Legacy namespace definitions will be overwritten.", + RunE: func(cmd *cobra.Command, args []string) error { + h, err := client.NewCommandHelper(cmd) + if err != nil { + return err + } + + file := flagx.MustGetString(cmd, "file") + + data, err := osx.ReadFileFromAllSources(file) + if err != nil { + return err + } + patch := fmt.Sprintf(`/services/permission/config/namespaces={"location": "base64://%s"}`, + base64.StdEncoding.EncodeToString(data)) + + projectID, err := client.ProjectID(cmd) + if err != nil { + return err + } + + p, err := h.PatchProject(projectID.String(), nil, nil, []string{patch}, nil) + if err != nil { + return cmdx.PrintOpenAPIError(cmd, err) + } + + cmdx.PrintJSONAble(cmd, outputConfig(p.Project.Services.Permission.Config)) + + return h.PrintUpdateProjectWarnings(p) + }, + } + + cmd.Flags().StringP("file", "f", "", + "Configuration file (file://namespace_config.ts, https://example.org/namespace_config.ts, ...) to update the Ory Permission Language config") + client.RegisterYesFlag(cmd.Flags()) + cmdx.RegisterFormatFlags(cmd.Flags()) + client.RegisterProjectFlag(cmd.Flags()) + + return cmd +} diff --git a/cmd/cloudx/project/update_namespace_config_test.go b/cmd/cloudx/project/update_namespace_config_test.go new file mode 100644 index 00000000..0926f545 --- /dev/null +++ b/cmd/cloudx/project/update_namespace_config_test.go @@ -0,0 +1,52 @@ +package project_test + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + + "github.com/ory/cli/cmd/cloudx/testhelpers" + "github.com/ory/x/fetcher" +) + +func writeFile(t *testing.T, content string) (path string) { + t.Helper() + + f, err := os.CreateTemp(t.TempDir(), "keto-namespaces-*.ts") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + if _, err := f.WriteString(content); err != nil { + t.Fatal(err) + } + + return f.Name() +} + +func TestUpdateNamespaceConfig(t *testing.T) { + project := testhelpers.CreateProject(t, defaultConfig) + content := `class Default implements Namespace {}` + config := writeFile(t, content) + verbs := []string{"update", "patch"} + + for _, verb := range verbs { + t.Run(fmt.Sprintf("is able to %q the namespace config", verb), func(t *testing.T) { + stdout, stderr, err := defaultCmd.Exec(nil, verb, "opl", "--project", project, "--format", "json", "--file", config) + require.NoError(t, err, stderr) + + if !testing.Short() { + // Don't download and compare the config in short mode, might not have internet everywhere + url := gjson.Get(stdout, "namespaces.location").String() + data, err := fetcher.NewFetcher().Fetch(url) + require.NoError(t, err, "could not download the config") + assert.Equal(t, content, data.String(), "the downloaded file does not match what we uploaded") + } + }) + } +} diff --git a/cmd/cloudx/project/update_test.go b/cmd/cloudx/project/update_test.go index 13172b22..f2f337a8 100644 --- a/cmd/cloudx/project/update_test.go +++ b/cmd/cloudx/project/update_test.go @@ -124,6 +124,9 @@ func TestUpdateProject(t *testing.T) { "serve.cookies.names", "serve.cookies.domain", "urls.self", + // bucket changes locally vs staging + "services.identity.config.identity.schemas.1.url", + "identity.schemas.1.url", )) }) diff --git a/cmd/cloudx/update.go b/cmd/cloudx/update.go index b9037b66..d0abcc55 100644 --- a/cmd/cloudx/update.go +++ b/cmd/cloudx/update.go @@ -19,6 +19,7 @@ func NewUpdateCmd() *cobra.Command { project.NewUpdateIdentityConfigCmd(), project.NewUpdateOAuth2ConfigCmd(), project.NewUpdatePermissionConfigCmd(), + project.NewUpdateNamespaceConfigCmd(), oauth2.NewUpdateOAuth2Client(), )