Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/flink/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func New(cfg *config.Config, prerunner pcmd.PreRunner) *cobra.Command {
cmd.AddCommand(c.newDetachedSavepointCommand())
cmd.AddCommand(c.newEnvironmentCommand())
cmd.AddCommand(c.newSavepointCommand())
cmd.AddCommand(c.newSecretCommand())

Comment thread
paras-negi-flink marked this conversation as resolved.
// On-Prem and Cloud Commands
cmd.AddCommand(c.newComputePoolCommand(cfg))
Expand Down
116 changes: 116 additions & 0 deletions internal/flink/command_secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package flink

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

cmfsdk "github.com/confluentinc/cmf-sdk-go/v1"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
"github.com/confluentinc/cli/v4/pkg/errors"
"github.com/confluentinc/cli/v4/pkg/output"
)

type secretOut struct {
CreationTime string `human:"Creation Time" serialized:"creation_time"`
Name string `human:"Name" serialized:"name"`
}

func (c *command) newSecretCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "secret",
Short: "Manage Flink secrets in Confluent Platform.",
Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogout},
}

cmd.AddCommand(c.newSecretCreateCommand())
cmd.AddCommand(c.newSecretDeleteCommand())
cmd.AddCommand(c.newSecretDescribeCommand())
cmd.AddCommand(c.newSecretListCommand())
cmd.AddCommand(c.newSecretUpdateCommand())
Comment on lines +31 to +35
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repo’s recursive help tests generate help fixtures for every available command/subcommand. Since flink secret introduces new leaf subcommands (create/delete/describe/list/update), you’ll also need to add the corresponding *-help-onprem.golden fixtures under test/fixtures/output/flink/secret/ (e.g., create-help-onprem.golden, delete-help-onprem.golden, etc.), otherwise TestHelp will fail on on-prem runs.

Copilot uses AI. Check for mistakes.

return cmd
}

func printSecretOutput(cmd *cobra.Command, sdkSecret cmfsdk.Secret) error {
if output.GetFormat(cmd) == output.Human {
table := output.NewTable(cmd)
var creationTime string
if sdkSecret.Metadata.CreationTimestamp != nil {
creationTime = *sdkSecret.Metadata.CreationTimestamp
}
table.Add(&secretOut{
CreationTime: creationTime,
Name: sdkSecret.Metadata.Name,
})
return table.Print()
}

localSecret := convertSdkSecretToLocalSecret(sdkSecret)
return output.SerializedOutput(cmd, localSecret)
}

func readSecretResourceFile(resourceFilePath string) (cmfsdk.Secret, error) {
data, err := os.ReadFile(resourceFilePath)
if err != nil {
return cmfsdk.Secret{}, fmt.Errorf("failed to read file: %w", err)
}

var genericData map[string]interface{}
ext := filepath.Ext(resourceFilePath)
switch ext {
case ".json":
err = json.Unmarshal(data, &genericData)
case ".yaml", ".yml":
err = yaml.Unmarshal(data, &genericData)
default:
return cmfsdk.Secret{}, errors.NewErrorWithSuggestions(fmt.Sprintf("unsupported file format: %s", ext), "Supported file formats are .json, .yaml, and .yml.")
}
if err != nil {
return cmfsdk.Secret{}, fmt.Errorf("failed to parse input file: %w", err)
}

jsonBytes, err := json.Marshal(genericData)
if err != nil {
return cmfsdk.Secret{}, fmt.Errorf("failed to marshal intermediate data: %w", err)
}

var sdkSecret cmfsdk.Secret
if err = json.Unmarshal(jsonBytes, &sdkSecret); err != nil {
return cmfsdk.Secret{}, fmt.Errorf("failed to bind data to Secret model: %w", err)
}

return sdkSecret, nil
}

func convertSdkSecretToLocalSecret(sdkSecret cmfsdk.Secret) LocalSecret {
localSecret := LocalSecret{
ApiVersion: sdkSecret.ApiVersion,
Kind: sdkSecret.Kind,
Metadata: LocalSecretMetadata{
Name: sdkSecret.Metadata.Name,
CreationTimestamp: sdkSecret.Metadata.CreationTimestamp,
UpdateTimestamp: sdkSecret.Metadata.UpdateTimestamp,
Uid: sdkSecret.Metadata.Uid,
Labels: sdkSecret.Metadata.Labels,
Annotations: sdkSecret.Metadata.Annotations,
},
Spec: LocalSecretSpec{
Data: sdkSecret.Spec.Data,
},
}

if sdkSecret.Status != nil {
localSecret.Status = &LocalSecretStatus{
Version: sdkSecret.Status.Version,
Environments: sdkSecret.Status.Environments,
}
}

return localSecret
}
43 changes: 43 additions & 0 deletions internal/flink/command_secret_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package flink

import (
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
)

func (c *command) newSecretCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create <resourceFilePath>",
Short: "Create a Flink secret.",
Long: "Create a Flink secret in Confluent Platform.",
Args: cobra.ExactArgs(1),
RunE: c.secretCreate,
}

addCmfFlagSet(cmd)
pcmd.AddOutputFlag(cmd)

return cmd
}

func (c *command) secretCreate(cmd *cobra.Command, args []string) error {
resourceFilePath := args[0]

client, err := c.GetCmfClient(cmd)
if err != nil {
return err
}

sdkSecret, err := readSecretResourceFile(resourceFilePath)
if err != nil {
return err
}

sdkOutputSecret, err := client.CreateSecret(c.createContext(), sdkSecret)
if err != nil {
return err
}

return printSecretOutput(cmd, sdkOutputSecret)
}
49 changes: 49 additions & 0 deletions internal/flink/command_secret_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package flink

import (
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
"github.com/confluentinc/cli/v4/pkg/deletion"
"github.com/confluentinc/cli/v4/pkg/errors"
"github.com/confluentinc/cli/v4/pkg/resource"
)

func (c *command) newSecretDeleteCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name-1> [name-2] ... [name-n]",
Short: "Delete a Flink secret in Confluent Platform.",
Args: cobra.MinimumNArgs(1),
RunE: c.secretDelete,
}

addCmfFlagSet(cmd)
pcmd.AddForceFlag(cmd)

return cmd
}

func (c *command) secretDelete(cmd *cobra.Command, args []string) error {
client, err := c.GetCmfClient(cmd)
if err != nil {
return err
}

existenceFunc := func(name string) bool {
_, err := client.DescribeSecret(c.createContext(), name)
return err == nil
}

if err := deletion.ValidateAndConfirm(cmd, args, existenceFunc, resource.FlinkSecret); err != nil {
suggestions := "List available Flink secrets with `confluent flink secret list`."
suggestions += "\nCheck that CMF is running and accessible."
return errors.NewErrorWithSuggestions(err.Error(), suggestions)
}

deleteFunc := func(name string) error {
return client.DeleteSecret(c.createContext(), name)
}

_, err = deletion.Delete(cmd, args, deleteFunc, resource.FlinkSecret)
return err
}
37 changes: 37 additions & 0 deletions internal/flink/command_secret_describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package flink

import (
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
)

func (c *command) newSecretDescribeCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "describe <name>",
Short: "Describe a Flink secret in Confluent Platform.",
Args: cobra.ExactArgs(1),
RunE: c.secretDescribe,
}

addCmfFlagSet(cmd)
pcmd.AddOutputFlag(cmd)

return cmd
}

func (c *command) secretDescribe(cmd *cobra.Command, args []string) error {
name := args[0]

client, err := c.GetCmfClient(cmd)
if err != nil {
return err
}

sdkOutputSecret, err := client.DescribeSecret(c.createContext(), name)
if err != nil {
return err
}

return printSecretOutput(cmd, sdkOutputSecret)
}
56 changes: 56 additions & 0 deletions internal/flink/command_secret_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package flink

import (
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
"github.com/confluentinc/cli/v4/pkg/output"
)

func (c *command) newSecretListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List Flink secrets in Confluent Platform.",
Args: cobra.NoArgs,
RunE: c.secretList,
}

addCmfFlagSet(cmd)
pcmd.AddOutputFlag(cmd)

return cmd
}

func (c *command) secretList(cmd *cobra.Command, _ []string) error {
client, err := c.GetCmfClient(cmd)
if err != nil {
return err
}

sdkSecrets, err := client.ListSecrets(c.createContext())
if err != nil {
return err
}

if output.GetFormat(cmd) == output.Human {
list := output.NewList(cmd)
for _, secret := range sdkSecrets {
var creationTime string
if secret.Metadata.CreationTimestamp != nil {
creationTime = *secret.Metadata.CreationTimestamp
}
list.Add(&secretOut{
CreationTime: creationTime,
Name: secret.Metadata.Name,
})
}
return list.Print()
}

localSecrets := make([]LocalSecret, 0, len(sdkSecrets))
for _, sdkSecret := range sdkSecrets {
localSecrets = append(localSecrets, convertSdkSecretToLocalSecret(sdkSecret))
}

return output.SerializedOutput(cmd, localSecrets)
}
50 changes: 50 additions & 0 deletions internal/flink/command_secret_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package flink

import (
"fmt"

"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
)

func (c *command) newSecretUpdateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <resourceFilePath>",
Short: "Update a Flink secret.",
Long: "Update a Flink secret in Confluent Platform.",
Args: cobra.ExactArgs(1),
RunE: c.secretUpdate,
}

addCmfFlagSet(cmd)
pcmd.AddOutputFlag(cmd)

return cmd
}

func (c *command) secretUpdate(cmd *cobra.Command, args []string) error {
resourceFilePath := args[0]

client, err := c.GetCmfClient(cmd)
if err != nil {
return err
}

sdkSecret, err := readSecretResourceFile(resourceFilePath)
if err != nil {
return err
}

secretName := sdkSecret.Metadata.Name
if secretName == "" {
return fmt.Errorf(`secret name is required: ensure the resource file contains a non-empty "metadata.name" field`)
}

sdkOutputSecret, err := client.UpdateSecret(c.createContext(), secretName, sdkSecret)
if err != nil {
return err
}

return printSecretOutput(cmd, sdkOutputSecret)
}
26 changes: 26 additions & 0 deletions internal/flink/local_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,32 @@ type LocalKafkaCatalogSpecSrInstance struct {
ConnectionSecretId *string `json:"connectionSecretId,omitempty" yaml:"connectionSecretId,omitempty"`
}

type LocalSecret struct {
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
Kind string `json:"kind" yaml:"kind"`
Metadata LocalSecretMetadata `json:"metadata" yaml:"metadata"`
Spec LocalSecretSpec `json:"spec" yaml:"spec"`
Status *LocalSecretStatus `json:"status,omitempty" yaml:"status,omitempty"`
}

type LocalSecretMetadata struct {
Name string `json:"name" yaml:"name"`
CreationTimestamp *string `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"`
UpdateTimestamp *string `json:"updateTimestamp,omitempty" yaml:"updateTimestamp,omitempty"`
Uid *string `json:"uid,omitempty" yaml:"uid,omitempty"`
Labels *map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
Annotations *map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
}

type LocalSecretSpec struct {
Data *map[string]string `json:"data,omitempty" yaml:"data,omitempty"`
}

type LocalSecretStatus struct {
Version *string `json:"version,omitempty" yaml:"version,omitempty"`
Environments *[]string `json:"environments,omitempty" yaml:"environments,omitempty"`
}

type LocalResultSchema struct {
Columns []LocalResultSchemaColumn `json:"columns" yaml:"columns"`
}
Expand Down
Loading