From e51aec4afd1db8559732346ff7120d496b0f66c3 Mon Sep 17 00:00:00 2001 From: Tushar Malik Date: Thu, 20 Aug 2026 17:26:41 -0400 Subject: [PATCH 1/2] DGS-25000 Add exporter cluster-link config describe command Adds `confluent schema-registry exporter configuration cluster-link describe`, which calls the new SR `GET /exporters/{name}/config/clusterlink` endpoint (confluentinc/schema-registry-plugins#2134) to derive the Cluster Link config(s) that replicate a schema exporter's subject/context translation. --- go.mod | 2 +- go.sum | 2 + .../command_exporter_configuration.go | 1 + ...and_exporter_configuration_cluster_link.go | 18 ++++++++ ...ter_configuration_cluster_link_describe.go | 46 +++++++++++++++++++ pkg/schemaregistry/client.go | 5 ++ .../cluster-link-describe-json.golden | 3 ++ .../cluster-link-describe-yaml.golden | 1 + test/schema_registry_test.go | 2 + test/test-server/schema_registry_handlers.go | 14 ++++++ test/test-server/schema_registry_router.go | 1 + 11 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 internal/schema-registry/command_exporter_configuration_cluster_link.go create mode 100644 internal/schema-registry/command_exporter_configuration_cluster_link_describe.go create mode 100644 test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-json.golden create mode 100644 test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-yaml.golden diff --git a/go.mod b/go.mod index a5f755576c..a0433162fd 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/confluentinc/mds-sdk-go-public/mdsv1 v0.0.0-20240923163156-b922b35891f9 github.com/confluentinc/mds-sdk-go-public/mdsv2alpha1 v0.0.0-20240923163156-b922b35891f9 github.com/confluentinc/properties v0.0.0-20190814194548-42c10394a787 - github.com/confluentinc/schema-registry-sdk-go v0.1.1-0.20251021214222-018e0cd35bf9 + github.com/confluentinc/schema-registry-sdk-go v0.1.3-0.20260812213011-262e9f97627b github.com/davecgh/go-spew v1.1.1 github.com/dghubble/sling v1.4.2 github.com/fatih/color v1.17.0 diff --git a/go.sum b/go.sum index fcdefcf035..60d1c937e9 100644 --- a/go.sum +++ b/go.sum @@ -265,6 +265,8 @@ github.com/confluentinc/proto-go-setter v0.3.0 h1:6BhgpDV5rWdEa+6LsnQWGV1udFboCa github.com/confluentinc/proto-go-setter v0.3.0/go.mod h1:WJkmDZf4f/nxGzjYJ+o3AsXI2hk64gLM5XlZrmlzs1s= github.com/confluentinc/schema-registry-sdk-go v0.1.1-0.20251021214222-018e0cd35bf9 h1:8rPUxlx0J0VVIOqF1pO25divMhyPZfD64bq1HDQnyUY= github.com/confluentinc/schema-registry-sdk-go v0.1.1-0.20251021214222-018e0cd35bf9/go.mod h1:yuuBwRHiqXeYbeuiUX8R7HgbquPJh8XTGpW7O08ErmU= +github.com/confluentinc/schema-registry-sdk-go v0.1.3-0.20260812213011-262e9f97627b h1:vfMHWJhsDWlzRTDFugCw1JZm8I6tWK9TABHYnxjvqqw= +github.com/confluentinc/schema-registry-sdk-go v0.1.3-0.20260812213011-262e9f97627b/go.mod h1:p5zwkMP5gyR4mgsoVQFjCI1EZMbYtxhzsJNnflrECPE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= diff --git a/internal/schema-registry/command_exporter_configuration.go b/internal/schema-registry/command_exporter_configuration.go index c76b9038d2..b37d90eaca 100644 --- a/internal/schema-registry/command_exporter_configuration.go +++ b/internal/schema-registry/command_exporter_configuration.go @@ -12,6 +12,7 @@ func (c *command) newExporterConfigurationCommand(cfg *config.Config) *cobra.Com Short: "Manage the schema exporter configuration.", } + cmd.AddCommand(c.newExporterConfigurationClusterLinkCommand(cfg)) cmd.AddCommand(c.newExporterConfigurationDescribeCommand(cfg)) return cmd diff --git a/internal/schema-registry/command_exporter_configuration_cluster_link.go b/internal/schema-registry/command_exporter_configuration_cluster_link.go new file mode 100644 index 0000000000..64cb16a574 --- /dev/null +++ b/internal/schema-registry/command_exporter_configuration_cluster_link.go @@ -0,0 +1,18 @@ +package schemaregistry + +import ( + "github.com/spf13/cobra" + + "github.com/confluentinc/cli/v4/pkg/config" +) + +func (c *command) newExporterConfigurationClusterLinkCommand(cfg *config.Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "cluster-link", + Short: "Manage the schema exporter's Cluster Link config.", + } + + cmd.AddCommand(c.newExporterConfigurationClusterLinkDescribeCommand(cfg)) + + return cmd +} diff --git a/internal/schema-registry/command_exporter_configuration_cluster_link_describe.go b/internal/schema-registry/command_exporter_configuration_cluster_link_describe.go new file mode 100644 index 0000000000..da9aba4724 --- /dev/null +++ b/internal/schema-registry/command_exporter_configuration_cluster_link_describe.go @@ -0,0 +1,46 @@ +package schemaregistry + +import ( + "github.com/spf13/cobra" + + pcmd "github.com/confluentinc/cli/v4/pkg/cmd" + "github.com/confluentinc/cli/v4/pkg/config" + "github.com/confluentinc/cli/v4/pkg/output" +) + +func (c *command) newExporterConfigurationClusterLinkDescribeCommand(cfg *config.Config) *cobra.Command { + cmd := &cobra.Command{ + Use: "describe ", + Short: "Describe the schema exporter's Cluster Link config.", + Long: "Derive the Cluster Link config(s) that replicate a schema exporter's subject/context translation, so they don't have to be hand-copied.", + Args: cobra.ExactArgs(1), + RunE: c.exporterConfigurationClusterLinkDescribe, + } + + pcmd.AddContextFlag(cmd, c.CLICommand) + if cfg.IsCloudLogin() { + pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand) + } else { + addCaLocationAndClientPathFlags(cmd) + } + addSchemaRegistryEndpointFlag(cmd) + pcmd.AddOutputFlagWithDefaultValue(cmd, output.JSON.String()) + + return cmd +} + +func (c *command) exporterConfigurationClusterLinkDescribe(cmd *cobra.Command, args []string) error { + client, err := c.GetSchemaRegistryClient(cmd) + if err != nil { + return err + } + + configs, err := client.GetExporterClusterLinkConfig(args[0]) + if err != nil { + return err + } + + table := output.NewTable(cmd) + table.Add(configs) + return table.Print() +} diff --git a/pkg/schemaregistry/client.go b/pkg/schemaregistry/client.go index 15cfe32e73..1a14ef6514 100644 --- a/pkg/schemaregistry/client.go +++ b/pkg/schemaregistry/client.go @@ -124,6 +124,11 @@ func (c *Client) GetExporterConfig(name string) (map[string]string, error) { return res, err } +func (c *Client) GetExporterClusterLinkConfig(name string) (map[string]string, error) { + res, _, err := c.DefaultApi.GetExporterClusterLinkConfig(c.context(), name).Execute() + return res, err +} + func (c *Client) ResumeExporter(name string) (srsdk.UpdateExporterResponse, error) { res, _, err := c.DefaultApi.ResumeExporter(c.context(), name).Execute() return res, err diff --git a/test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-json.golden b/test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-json.golden new file mode 100644 index 0000000000..9b3ffd325c --- /dev/null +++ b/test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-json.golden @@ -0,0 +1,3 @@ +{ + "topic.config.sync.associations.filters": "{\"topicsToInclude\":[\"*\"],\"topicsToExclude\":[],\"contextType\":\"AUTO\",\"subjectRenameFormat\":\"my-${subject}\",\"sourceSRDeployment\":\"CONFLUENT_CLOUD\",\"sourceLSRC\":\"lsrc-abc123\"}" +} diff --git a/test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-yaml.golden b/test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-yaml.golden new file mode 100644 index 0000000000..195faffc71 --- /dev/null +++ b/test/fixtures/output/schema-registry/exporter/configuration/cluster-link-describe-yaml.golden @@ -0,0 +1 @@ +topic.config.sync.associations.filters: '{"topicsToInclude":["*"],"topicsToExclude":[],"contextType":"AUTO","subjectRenameFormat":"my-${subject}","sourceSRDeployment":"CONFLUENT_CLOUD","sourceLSRC":"lsrc-abc123"}' diff --git a/test/schema_registry_test.go b/test/schema_registry_test.go index d3d9413c1a..474cefeb8d 100644 --- a/test/schema_registry_test.go +++ b/test/schema_registry_test.go @@ -91,6 +91,8 @@ func (s *CLITestSuite) TestSchemaRegistryExporter() { {args: fmt.Sprintf(`schema-registry exporter delete myexporter myexporter2 --environment %s --force`, testserver.SRApiEnvId), fixture: "schema-registry/exporter/delete-multiple-success.golden"}, {args: fmt.Sprintf("schema-registry exporter delete myexporter --environment %s", testserver.SRApiEnvId), input: "y\n", fixture: "schema-registry/exporter/delete-prompt.golden"}, {args: fmt.Sprintf("schema-registry exporter status describe myexporter --environment %s", testserver.SRApiEnvId), fixture: "schema-registry/exporter/status/describe.golden"}, + {args: fmt.Sprintf("schema-registry exporter configuration cluster-link describe myexporter --output json --environment %s", testserver.SRApiEnvId), fixture: "schema-registry/exporter/configuration/cluster-link-describe-json.golden"}, + {args: fmt.Sprintf("schema-registry exporter configuration cluster-link describe myexporter --output yaml --environment %s", testserver.SRApiEnvId), fixture: "schema-registry/exporter/configuration/cluster-link-describe-yaml.golden"}, {args: fmt.Sprintf("schema-registry exporter configuration describe myexporter --output json --environment %s", testserver.SRApiEnvId), fixture: "schema-registry/exporter/configuration/describe-json.golden"}, {args: fmt.Sprintf("schema-registry exporter configuration describe myexporter --output yaml --environment %s", testserver.SRApiEnvId), fixture: "schema-registry/exporter/configuration/describe-yaml.golden"}, {args: fmt.Sprintf("schema-registry exporter pause myexporter --environment %s", testserver.SRApiEnvId), fixture: "schema-registry/exporter/pause.golden"}, diff --git a/test/test-server/schema_registry_handlers.go b/test/test-server/schema_registry_handlers.go index aec3c26165..c117e5d44a 100644 --- a/test/test-server/schema_registry_handlers.go +++ b/test/test-server/schema_registry_handlers.go @@ -389,6 +389,20 @@ func handleSRExporterConfig(t *testing.T) http.HandlerFunc { } } +// Handler for: "/exporters/{name}/config/clusterlink" +func handleSRExporterClusterLinkConfig(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + filterConfig := `{"topicsToInclude":["*"],"topicsToExclude":[],"contextType":"AUTO",` + + `"subjectRenameFormat":"my-${subject}","sourceSRDeployment":"CONFLUENT_CLOUD",` + + `"sourceLSRC":"lsrc-abc123"}` + config := map[string]string{ + "topic.config.sync.associations.filters": filterConfig, + } + err := json.NewEncoder(w).Encode(config) + require.NoError(t, err) + } +} + // Handler for: "/exporters/{name}/pause" func handleSRExporterPause(t *testing.T) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { diff --git a/test/test-server/schema_registry_router.go b/test/test-server/schema_registry_router.go index 911331df63..337b2b61cb 100644 --- a/test/test-server/schema_registry_router.go +++ b/test/test-server/schema_registry_router.go @@ -29,6 +29,7 @@ var schemaRegistryRoutes = []route{ {"/exporters/{name}", handleSRExporter}, {"/exporters/{name}/status", handleSRExporterStatus}, {"/exporters/{name}/config", handleSRExporterConfig}, + {"/exporters/{name}/config/clusterlink", handleSRExporterClusterLinkConfig}, {"/exporters/{name}/pause", handleSRExporterPause}, {"/exporters/{name}/resume", handleSRExporterResume}, {"/exporters/{name}/reset", handleSRExporterReset}, From 78a6b5347563bc11f234824f88a1775e4f28ebf1 Mon Sep 17 00:00:00 2001 From: Tushar Malik Date: Tue, 1 Sep 2026 17:49:28 -0500 Subject: [PATCH 2/2] Split cluster-link config commands into cloud/on-prem, per review Adds command_exporter_configuration_cluster_link_onprem.go and command_exporter_configuration_cluster_link_describe_onprem.go, mirroring the existing cloud/on-prem sibling-file pattern (e.g. command_topic_configuration_list.go/_onprem.go) instead of an inline if/else, since the backend endpoint (schema-registry-plugins#2134) is genuinely available on both Confluent Cloud and Confluent Platform. command_exporter_configuration.go now if-switches between the two based on cfg.IsCloudLogin(). The cloud variant carries RequireCloudLogin; the on-prem variant carries no annotation, matching the existing sibling command_exporter_configuration_describe.go, which also connects directly via --schema-registry-endpoint/cert flags with no MDS login required. Manually verified end-to-end against a real on-prem Schema Registry (schema-exporter module, real local Kafka, real exporter) via the actual built CLI binary, in addition to the existing cloud verification. --- .../command_exporter_configuration.go | 6 ++++- ...and_exporter_configuration_cluster_link.go | 11 ++++---- ...ter_configuration_cluster_link_describe.go | 9 ++----- ...figuration_cluster_link_describe_onprem.go | 25 +++++++++++++++++++ ...orter_configuration_cluster_link_onprem.go | 19 ++++++++++++++ 5 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 internal/schema-registry/command_exporter_configuration_cluster_link_describe_onprem.go create mode 100644 internal/schema-registry/command_exporter_configuration_cluster_link_onprem.go diff --git a/internal/schema-registry/command_exporter_configuration.go b/internal/schema-registry/command_exporter_configuration.go index b37d90eaca..1af30931be 100644 --- a/internal/schema-registry/command_exporter_configuration.go +++ b/internal/schema-registry/command_exporter_configuration.go @@ -12,7 +12,11 @@ func (c *command) newExporterConfigurationCommand(cfg *config.Config) *cobra.Com Short: "Manage the schema exporter configuration.", } - cmd.AddCommand(c.newExporterConfigurationClusterLinkCommand(cfg)) + if cfg.IsCloudLogin() { + cmd.AddCommand(c.newExporterConfigurationClusterLinkCommand()) + } else { + cmd.AddCommand(c.newExporterConfigurationClusterLinkCommandOnPrem()) + } cmd.AddCommand(c.newExporterConfigurationDescribeCommand(cfg)) return cmd diff --git a/internal/schema-registry/command_exporter_configuration_cluster_link.go b/internal/schema-registry/command_exporter_configuration_cluster_link.go index 64cb16a574..073dcfa753 100644 --- a/internal/schema-registry/command_exporter_configuration_cluster_link.go +++ b/internal/schema-registry/command_exporter_configuration_cluster_link.go @@ -3,16 +3,17 @@ package schemaregistry import ( "github.com/spf13/cobra" - "github.com/confluentinc/cli/v4/pkg/config" + pcmd "github.com/confluentinc/cli/v4/pkg/cmd" ) -func (c *command) newExporterConfigurationClusterLinkCommand(cfg *config.Config) *cobra.Command { +func (c *command) newExporterConfigurationClusterLinkCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "cluster-link", - Short: "Manage the schema exporter's Cluster Link config.", + Use: "cluster-link", + Short: "Manage the schema exporter's Cluster Link config.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, } - cmd.AddCommand(c.newExporterConfigurationClusterLinkDescribeCommand(cfg)) + cmd.AddCommand(c.newExporterConfigurationClusterLinkDescribeCommand()) return cmd } diff --git a/internal/schema-registry/command_exporter_configuration_cluster_link_describe.go b/internal/schema-registry/command_exporter_configuration_cluster_link_describe.go index da9aba4724..dd1d53eb0d 100644 --- a/internal/schema-registry/command_exporter_configuration_cluster_link_describe.go +++ b/internal/schema-registry/command_exporter_configuration_cluster_link_describe.go @@ -4,11 +4,10 @@ import ( "github.com/spf13/cobra" pcmd "github.com/confluentinc/cli/v4/pkg/cmd" - "github.com/confluentinc/cli/v4/pkg/config" "github.com/confluentinc/cli/v4/pkg/output" ) -func (c *command) newExporterConfigurationClusterLinkDescribeCommand(cfg *config.Config) *cobra.Command { +func (c *command) newExporterConfigurationClusterLinkDescribeCommand() *cobra.Command { cmd := &cobra.Command{ Use: "describe ", Short: "Describe the schema exporter's Cluster Link config.", @@ -18,11 +17,7 @@ func (c *command) newExporterConfigurationClusterLinkDescribeCommand(cfg *config } pcmd.AddContextFlag(cmd, c.CLICommand) - if cfg.IsCloudLogin() { - pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand) - } else { - addCaLocationAndClientPathFlags(cmd) - } + pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand) addSchemaRegistryEndpointFlag(cmd) pcmd.AddOutputFlagWithDefaultValue(cmd, output.JSON.String()) diff --git a/internal/schema-registry/command_exporter_configuration_cluster_link_describe_onprem.go b/internal/schema-registry/command_exporter_configuration_cluster_link_describe_onprem.go new file mode 100644 index 0000000000..bdc88c3b1b --- /dev/null +++ b/internal/schema-registry/command_exporter_configuration_cluster_link_describe_onprem.go @@ -0,0 +1,25 @@ +package schemaregistry + +import ( + "github.com/spf13/cobra" + + pcmd "github.com/confluentinc/cli/v4/pkg/cmd" + "github.com/confluentinc/cli/v4/pkg/output" +) + +func (c *command) newExporterConfigurationClusterLinkDescribeCommandOnPrem() *cobra.Command { + cmd := &cobra.Command{ + Use: "describe ", + Short: "Describe the schema exporter's Cluster Link config.", + Long: "Derive the Cluster Link config(s) that replicate a schema exporter's subject/context translation, so they don't have to be hand-copied.", + Args: cobra.ExactArgs(1), + RunE: c.exporterConfigurationClusterLinkDescribe, + } + + pcmd.AddContextFlag(cmd, c.CLICommand) + addCaLocationAndClientPathFlags(cmd) + addSchemaRegistryEndpointFlag(cmd) + pcmd.AddOutputFlagWithDefaultValue(cmd, output.JSON.String()) + + return cmd +} diff --git a/internal/schema-registry/command_exporter_configuration_cluster_link_onprem.go b/internal/schema-registry/command_exporter_configuration_cluster_link_onprem.go new file mode 100644 index 0000000000..7f25e285d1 --- /dev/null +++ b/internal/schema-registry/command_exporter_configuration_cluster_link_onprem.go @@ -0,0 +1,19 @@ +package schemaregistry + +import ( + "github.com/spf13/cobra" +) + +// No RunRequirement annotation: like its sibling command_exporter_configuration_describe.go, +// this connects directly to a Schema Registry endpoint via --schema-registry-endpoint and +// certificate flags, with no MDS login required. +func (c *command) newExporterConfigurationClusterLinkCommandOnPrem() *cobra.Command { + cmd := &cobra.Command{ + Use: "cluster-link", + Short: "Manage the schema exporter's Cluster Link config.", + } + + cmd.AddCommand(c.newExporterConfigurationClusterLinkDescribeCommandOnPrem()) + + return cmd +}