diff --git a/internal/flink/command.go b/internal/flink/command.go index b5cd215b75..ab5a9326db 100644 --- a/internal/flink/command.go +++ b/internal/flink/command.go @@ -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()) // On-Prem and Cloud Commands cmd.AddCommand(c.newComputePoolCommand(cfg)) diff --git a/internal/flink/command_secret.go b/internal/flink/command_secret.go new file mode 100644 index 0000000000..b0070bf77f --- /dev/null +++ b/internal/flink/command_secret.go @@ -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()) + + 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 +} diff --git a/internal/flink/command_secret_create.go b/internal/flink/command_secret_create.go new file mode 100644 index 0000000000..fe7ed3b073 --- /dev/null +++ b/internal/flink/command_secret_create.go @@ -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 ", + 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) +} diff --git a/internal/flink/command_secret_delete.go b/internal/flink/command_secret_delete.go new file mode 100644 index 0000000000..acfb3db450 --- /dev/null +++ b/internal/flink/command_secret_delete.go @@ -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-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 +} diff --git a/internal/flink/command_secret_describe.go b/internal/flink/command_secret_describe.go new file mode 100644 index 0000000000..a128c3ce1a --- /dev/null +++ b/internal/flink/command_secret_describe.go @@ -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 ", + 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) +} diff --git a/internal/flink/command_secret_list.go b/internal/flink/command_secret_list.go new file mode 100644 index 0000000000..c02c7151b0 --- /dev/null +++ b/internal/flink/command_secret_list.go @@ -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) +} diff --git a/internal/flink/command_secret_update.go b/internal/flink/command_secret_update.go new file mode 100644 index 0000000000..729257bf74 --- /dev/null +++ b/internal/flink/command_secret_update.go @@ -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 ", + 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) +} diff --git a/internal/flink/local_types.go b/internal/flink/local_types.go index 19e0b756d9..5b3ab2b8e0 100644 --- a/internal/flink/local_types.go +++ b/internal/flink/local_types.go @@ -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"` } diff --git a/pkg/flink/cmf_rest_client.go b/pkg/flink/cmf_rest_client.go index 97d68971eb..73bcdf9d83 100644 --- a/pkg/flink/cmf_rest_client.go +++ b/pkg/flink/cmf_rest_client.go @@ -570,6 +570,54 @@ func (cmfClient *CmfRestClient) DeleteCatalog(ctx context.Context, catalogName s return parseSdkError(httpResp, err) } +func (cmfClient *CmfRestClient) CreateSecret(ctx context.Context, secret cmfsdk.Secret) (cmfsdk.Secret, error) { + secretName := secret.Metadata.Name + outputSecret, httpResponse, err := cmfClient.SecretsApi.CreateSecret(ctx).Secret(secret).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return cmfsdk.Secret{}, fmt.Errorf(`failed to create secret "%s": %s`, secretName, parsedErr) + } + return outputSecret, nil +} + +func (cmfClient *CmfRestClient) DescribeSecret(ctx context.Context, secretName string) (cmfsdk.Secret, error) { + outputSecret, httpResponse, err := cmfClient.SecretsApi.GetSecret(ctx, secretName).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return cmfsdk.Secret{}, fmt.Errorf(`failed to get secret "%s": %s`, secretName, parsedErr) + } + return outputSecret, nil +} + +func (cmfClient *CmfRestClient) ListSecrets(ctx context.Context) ([]cmfsdk.Secret, error) { + secrets := make([]cmfsdk.Secret, 0) + done := false + const pageSize = 100 + var currentPageNumber int32 = 0 + + for !done { + secretsPage, httpResponse, err := cmfClient.SecretsApi.GetSecrets(ctx).Page(currentPageNumber).Size(pageSize).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return nil, fmt.Errorf(`failed to list secrets: %s`, parsedErr) + } + secrets = append(secrets, secretsPage.GetItems()...) + currentPageNumber, done = extractPageOptions(len(secretsPage.GetItems()), currentPageNumber) + } + + return secrets, nil +} + +func (cmfClient *CmfRestClient) UpdateSecret(ctx context.Context, secretName string, secret cmfsdk.Secret) (cmfsdk.Secret, error) { + outputSecret, httpResponse, err := cmfClient.SecretsApi.UpdateSecret(ctx, secretName).Secret(secret).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return cmfsdk.Secret{}, fmt.Errorf(`failed to update secret "%s": %s`, secretName, parsedErr) + } + return outputSecret, nil +} + +func (cmfClient *CmfRestClient) DeleteSecret(ctx context.Context, secretName string) error { + httpResp, err := cmfClient.SecretsApi.DeleteSecret(ctx, secretName).Execute() + return parseSdkError(httpResp, err) +} + // Returns the next page number and whether we need to fetch more pages or not. func extractPageOptions(receivedItemsLength int, currentPageNumber int32) (int32, bool) { if receivedItemsLength == 0 { diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index a56592ddd9..ba44c0d9f1 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -50,6 +50,7 @@ const ( FlinkEndpoint = "Flink endpoint" FlinkStatement = "Flink SQL statement" FlinkConnection = "Flink connection" + FlinkSecret = "Flink secret" Gateway = "gateway" IdentityPool = "identity pool" IdentityProvider = "identity provider" diff --git a/test/fixtures/input/flink/secret/create-invalid-failure.json b/test/fixtures/input/flink/secret/create-invalid-failure.json new file mode 100644 index 0000000000..a8ffee12c5 --- /dev/null +++ b/test/fixtures/input/flink/secret/create-invalid-failure.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "invalid-secret" + }, + "spec": { + "data": {} + } +} diff --git a/test/fixtures/input/flink/secret/create-invalid-failure.yaml b/test/fixtures/input/flink/secret/create-invalid-failure.yaml new file mode 100644 index 0000000000..cab786691d --- /dev/null +++ b/test/fixtures/input/flink/secret/create-invalid-failure.yaml @@ -0,0 +1,6 @@ +apiVersion: cmf/v1 +kind: Secret +metadata: + name: invalid-secret +spec: + data: {} diff --git a/test/fixtures/input/flink/secret/create-successful.json b/test/fixtures/input/flink/secret/create-successful.json new file mode 100644 index 0000000000..4f64c08f19 --- /dev/null +++ b/test/fixtures/input/flink/secret/create-successful.json @@ -0,0 +1,13 @@ +{ + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "test-secret" + }, + "spec": { + "data": { + "bootstrap.servers": "pkc-abc123.us-west-2.aws.confluent.cloud:9092", + "sasl.jaas.config": "org.apache.flink.kafka.shaded.org.apache.kafka.common.security.plain.PlainLoginModule required username='key' password='secret';" + } + } +} diff --git a/test/fixtures/input/flink/secret/create-successful.yaml b/test/fixtures/input/flink/secret/create-successful.yaml new file mode 100644 index 0000000000..3ebe7f0f94 --- /dev/null +++ b/test/fixtures/input/flink/secret/create-successful.yaml @@ -0,0 +1,8 @@ +apiVersion: cmf/v1 +kind: Secret +metadata: + name: test-secret +spec: + data: + bootstrap.servers: "pkc-abc123.us-west-2.aws.confluent.cloud:9092" + sasl.jaas.config: "org.apache.flink.kafka.shaded.org.apache.kafka.common.security.plain.PlainLoginModule required username='key' password='secret';" diff --git a/test/fixtures/input/flink/secret/update-invalid-failure.json b/test/fixtures/input/flink/secret/update-invalid-failure.json new file mode 100644 index 0000000000..a8ffee12c5 --- /dev/null +++ b/test/fixtures/input/flink/secret/update-invalid-failure.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "invalid-secret" + }, + "spec": { + "data": {} + } +} diff --git a/test/fixtures/input/flink/secret/update-invalid-failure.yaml b/test/fixtures/input/flink/secret/update-invalid-failure.yaml new file mode 100644 index 0000000000..cab786691d --- /dev/null +++ b/test/fixtures/input/flink/secret/update-invalid-failure.yaml @@ -0,0 +1,6 @@ +apiVersion: cmf/v1 +kind: Secret +metadata: + name: invalid-secret +spec: + data: {} diff --git a/test/fixtures/input/flink/secret/update-successful.json b/test/fixtures/input/flink/secret/update-successful.json new file mode 100644 index 0000000000..c6800a2ac2 --- /dev/null +++ b/test/fixtures/input/flink/secret/update-successful.json @@ -0,0 +1,13 @@ +{ + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "test-secret" + }, + "spec": { + "data": { + "bootstrap.servers": "pkc-abc123.us-west-2.aws.confluent.cloud:9092", + "sasl.jaas.config": "org.apache.flink.kafka.shaded.org.apache.kafka.common.security.plain.PlainLoginModule required username='key' password='new-secret';" + } + } +} diff --git a/test/fixtures/input/flink/secret/update-successful.yaml b/test/fixtures/input/flink/secret/update-successful.yaml new file mode 100644 index 0000000000..561be607b0 --- /dev/null +++ b/test/fixtures/input/flink/secret/update-successful.yaml @@ -0,0 +1,8 @@ +apiVersion: cmf/v1 +kind: Secret +metadata: + name: test-secret +spec: + data: + bootstrap.servers: "pkc-abc123.us-west-2.aws.confluent.cloud:9092" + sasl.jaas.config: "org.apache.flink.kafka.shaded.org.apache.kafka.common.security.plain.PlainLoginModule required username='key' password='new-secret';" diff --git a/test/fixtures/output/flink/help-onprem.golden b/test/fixtures/output/flink/help-onprem.golden index 7a1902639d..4156dd9439 100644 --- a/test/fixtures/output/flink/help-onprem.golden +++ b/test/fixtures/output/flink/help-onprem.golden @@ -10,6 +10,7 @@ Available Commands: detached-savepoint Manage Flink detached savepoints. environment Manage Flink environments. savepoint Manage Flink savepoints. + secret Manage Flink secrets in Confluent Platform. statement Manage Flink SQL statements. Global Flags: diff --git a/test/fixtures/output/flink/secret/create-help-onprem.golden b/test/fixtures/output/flink/secret/create-help-onprem.golden new file mode 100644 index 0000000000..bd76d2b1bd --- /dev/null +++ b/test/fixtures/output/flink/secret/create-help-onprem.golden @@ -0,0 +1,16 @@ +Create a Flink secret in Confluent Platform. + +Usage: + confluent flink secret create [flags] + +Flags: + --url string Base URL of the Confluent Manager for Apache Flink (CMF). Environment variable "CONFLUENT_CMF_URL" may be set in place of this flag. + --client-key-path string Path to client private key for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_KEY_PATH" may be set in place of this flag. + --client-cert-path string Path to client cert to be verified by Confluent Manager for Apache Flink. Include for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_CERT_PATH" may be set in place of this flag. + --certificate-authority-path string Path to a PEM-encoded Certificate Authority to verify the Confluent Manager for Apache Flink connection. Environment variable "CONFLUENT_CMF_CERTIFICATE_AUTHORITY_PATH" may be set in place of this flag. + -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). diff --git a/test/fixtures/output/flink/secret/create-invalid-failure.golden b/test/fixtures/output/flink/secret/create-invalid-failure.golden new file mode 100644 index 0000000000..8d1a7de55a --- /dev/null +++ b/test/fixtures/output/flink/secret/create-invalid-failure.golden @@ -0,0 +1 @@ +Error: failed to create secret "invalid-secret": The Secret object from resource file is invalid diff --git a/test/fixtures/output/flink/secret/create-success-json.golden b/test/fixtures/output/flink/secret/create-success-json.golden new file mode 100644 index 0000000000..3a12a59ef5 --- /dev/null +++ b/test/fixtures/output/flink/secret/create-success-json.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "test-secret", + "creationTimestamp": "2025-03-12 23:42:00 +0000 UTC" + }, + "spec": { + "data": { + "bootstrap.servers": "****", + "sasl.jaas.config": "****" + } + } +} diff --git a/test/fixtures/output/flink/secret/create-success-yaml.golden b/test/fixtures/output/flink/secret/create-success-yaml.golden new file mode 100644 index 0000000000..85cfe883ef --- /dev/null +++ b/test/fixtures/output/flink/secret/create-success-yaml.golden @@ -0,0 +1,9 @@ +apiVersion: cmf/v1 +kind: Secret +metadata: + name: test-secret + creationTimestamp: 2025-03-12 23:42:00 +0000 UTC +spec: + data: + bootstrap.servers: '****' + sasl.jaas.config: '****' diff --git a/test/fixtures/output/flink/secret/create-success.golden b/test/fixtures/output/flink/secret/create-success.golden new file mode 100644 index 0000000000..fd30a5e34d --- /dev/null +++ b/test/fixtures/output/flink/secret/create-success.golden @@ -0,0 +1,4 @@ ++---------------+-------------------------------+ +| Creation Time | 2025-03-12 23:42:00 +0000 UTC | +| Name | test-secret | ++---------------+-------------------------------+ diff --git a/test/fixtures/output/flink/secret/delete-help-onprem.golden b/test/fixtures/output/flink/secret/delete-help-onprem.golden new file mode 100644 index 0000000000..4478bb6cb2 --- /dev/null +++ b/test/fixtures/output/flink/secret/delete-help-onprem.golden @@ -0,0 +1,16 @@ +Delete a Flink secret in Confluent Platform. + +Usage: + confluent flink secret delete [name-2] ... [name-n] [flags] + +Flags: + --url string Base URL of the Confluent Manager for Apache Flink (CMF). Environment variable "CONFLUENT_CMF_URL" may be set in place of this flag. + --client-key-path string Path to client private key for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_KEY_PATH" may be set in place of this flag. + --client-cert-path string Path to client cert to be verified by Confluent Manager for Apache Flink. Include for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_CERT_PATH" may be set in place of this flag. + --certificate-authority-path string Path to a PEM-encoded Certificate Authority to verify the Confluent Manager for Apache Flink connection. Environment variable "CONFLUENT_CMF_CERTIFICATE_AUTHORITY_PATH" may be set in place of this flag. + --force Skip the deletion confirmation prompt. + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). diff --git a/test/fixtures/output/flink/secret/delete-non-exist-failure.golden b/test/fixtures/output/flink/secret/delete-non-exist-failure.golden new file mode 100644 index 0000000000..3b0117e264 --- /dev/null +++ b/test/fixtures/output/flink/secret/delete-non-exist-failure.golden @@ -0,0 +1 @@ +Are you sure you want to delete Flink secret "non-exist-secret"? (y/n): Error: failed to delete non-exist-secret: 404 Not Found diff --git a/test/fixtures/output/flink/secret/delete-single-force.golden b/test/fixtures/output/flink/secret/delete-single-force.golden new file mode 100644 index 0000000000..fe027dfc2d --- /dev/null +++ b/test/fixtures/output/flink/secret/delete-single-force.golden @@ -0,0 +1 @@ +Deleted Flink secret "test-secret-1". diff --git a/test/fixtures/output/flink/secret/delete-single-successful.golden b/test/fixtures/output/flink/secret/delete-single-successful.golden new file mode 100644 index 0000000000..5fab9b62e2 --- /dev/null +++ b/test/fixtures/output/flink/secret/delete-single-successful.golden @@ -0,0 +1 @@ +Are you sure you want to delete Flink secret "test-secret-1"? (y/n): Deleted Flink secret "test-secret-1". diff --git a/test/fixtures/output/flink/secret/describe-help-onprem.golden b/test/fixtures/output/flink/secret/describe-help-onprem.golden new file mode 100644 index 0000000000..90ae883a94 --- /dev/null +++ b/test/fixtures/output/flink/secret/describe-help-onprem.golden @@ -0,0 +1,16 @@ +Describe a Flink secret in Confluent Platform. + +Usage: + confluent flink secret describe [flags] + +Flags: + --url string Base URL of the Confluent Manager for Apache Flink (CMF). Environment variable "CONFLUENT_CMF_URL" may be set in place of this flag. + --client-key-path string Path to client private key for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_KEY_PATH" may be set in place of this flag. + --client-cert-path string Path to client cert to be verified by Confluent Manager for Apache Flink. Include for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_CERT_PATH" may be set in place of this flag. + --certificate-authority-path string Path to a PEM-encoded Certificate Authority to verify the Confluent Manager for Apache Flink connection. Environment variable "CONFLUENT_CMF_CERTIFICATE_AUTHORITY_PATH" may be set in place of this flag. + -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). diff --git a/test/fixtures/output/flink/secret/describe-not-found.golden b/test/fixtures/output/flink/secret/describe-not-found.golden new file mode 100644 index 0000000000..05036d5a9c --- /dev/null +++ b/test/fixtures/output/flink/secret/describe-not-found.golden @@ -0,0 +1 @@ +Error: failed to get secret "invalid-secret": The secret name is invalid diff --git a/test/fixtures/output/flink/secret/describe-success-json.golden b/test/fixtures/output/flink/secret/describe-success-json.golden new file mode 100644 index 0000000000..45f57f8883 --- /dev/null +++ b/test/fixtures/output/flink/secret/describe-success-json.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "test-secret", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "data": { + "bootstrap.servers": "****", + "sasl.jaas.config": "****" + } + } +} diff --git a/test/fixtures/output/flink/secret/describe-success-yaml.golden b/test/fixtures/output/flink/secret/describe-success-yaml.golden new file mode 100644 index 0000000000..0fc5d60dc5 --- /dev/null +++ b/test/fixtures/output/flink/secret/describe-success-yaml.golden @@ -0,0 +1,9 @@ +apiVersion: cmf/v1 +kind: Secret +metadata: + name: test-secret + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC +spec: + data: + bootstrap.servers: '****' + sasl.jaas.config: '****' diff --git a/test/fixtures/output/flink/secret/describe-success.golden b/test/fixtures/output/flink/secret/describe-success.golden new file mode 100644 index 0000000000..8773d2cb62 --- /dev/null +++ b/test/fixtures/output/flink/secret/describe-success.golden @@ -0,0 +1,4 @@ ++---------------+-------------------------------+ +| Creation Time | 2025-08-05 12:00:00 +0000 UTC | +| Name | test-secret | ++---------------+-------------------------------+ diff --git a/test/fixtures/output/flink/secret/help-onprem.golden b/test/fixtures/output/flink/secret/help-onprem.golden new file mode 100644 index 0000000000..ef27eda7d7 --- /dev/null +++ b/test/fixtures/output/flink/secret/help-onprem.golden @@ -0,0 +1,18 @@ +Manage Flink secrets in Confluent Platform. + +Usage: + confluent flink secret [command] + +Available Commands: + create Create a Flink secret. + delete Delete a Flink secret in Confluent Platform. + describe Describe a Flink secret in Confluent Platform. + list List Flink secrets in Confluent Platform. + update Update a Flink secret. + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). + +Use "confluent flink secret [command] --help" for more information about a command. diff --git a/test/fixtures/output/flink/secret/list-help-onprem.golden b/test/fixtures/output/flink/secret/list-help-onprem.golden new file mode 100644 index 0000000000..c9b6c95e8a --- /dev/null +++ b/test/fixtures/output/flink/secret/list-help-onprem.golden @@ -0,0 +1,16 @@ +List Flink secrets in Confluent Platform. + +Usage: + confluent flink secret list [flags] + +Flags: + --url string Base URL of the Confluent Manager for Apache Flink (CMF). Environment variable "CONFLUENT_CMF_URL" may be set in place of this flag. + --client-key-path string Path to client private key for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_KEY_PATH" may be set in place of this flag. + --client-cert-path string Path to client cert to be verified by Confluent Manager for Apache Flink. Include for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_CERT_PATH" may be set in place of this flag. + --certificate-authority-path string Path to a PEM-encoded Certificate Authority to verify the Confluent Manager for Apache Flink connection. Environment variable "CONFLUENT_CMF_CERTIFICATE_AUTHORITY_PATH" may be set in place of this flag. + -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). diff --git a/test/fixtures/output/flink/secret/list-success-json.golden b/test/fixtures/output/flink/secret/list-success-json.golden new file mode 100644 index 0000000000..f3e83601e8 --- /dev/null +++ b/test/fixtures/output/flink/secret/list-success-json.golden @@ -0,0 +1,30 @@ +[ + { + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "test-secret-1", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "data": { + "bootstrap.servers": "****", + "sasl.jaas.config": "****" + } + } + }, + { + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "test-secret-2", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "data": { + "bootstrap.servers": "****", + "sasl.jaas.config": "****" + } + } + } +] diff --git a/test/fixtures/output/flink/secret/list-success-yaml.golden b/test/fixtures/output/flink/secret/list-success-yaml.golden new file mode 100644 index 0000000000..dfb4a32f3b --- /dev/null +++ b/test/fixtures/output/flink/secret/list-success-yaml.golden @@ -0,0 +1,18 @@ +- apiVersion: cmf/v1 + kind: Secret + metadata: + name: test-secret-1 + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC + spec: + data: + bootstrap.servers: '****' + sasl.jaas.config: '****' +- apiVersion: cmf/v1 + kind: Secret + metadata: + name: test-secret-2 + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC + spec: + data: + bootstrap.servers: '****' + sasl.jaas.config: '****' diff --git a/test/fixtures/output/flink/secret/list-success.golden b/test/fixtures/output/flink/secret/list-success.golden new file mode 100644 index 0000000000..614471632b --- /dev/null +++ b/test/fixtures/output/flink/secret/list-success.golden @@ -0,0 +1,4 @@ + Creation Time | Name +--------------------------------+---------------- + 2025-08-05 12:00:00 +0000 UTC | test-secret-1 + 2025-08-05 12:00:00 +0000 UTC | test-secret-2 diff --git a/test/fixtures/output/flink/secret/update-help-onprem.golden b/test/fixtures/output/flink/secret/update-help-onprem.golden new file mode 100644 index 0000000000..89bba5ed0f --- /dev/null +++ b/test/fixtures/output/flink/secret/update-help-onprem.golden @@ -0,0 +1,16 @@ +Update a Flink secret in Confluent Platform. + +Usage: + confluent flink secret update [flags] + +Flags: + --url string Base URL of the Confluent Manager for Apache Flink (CMF). Environment variable "CONFLUENT_CMF_URL" may be set in place of this flag. + --client-key-path string Path to client private key for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_KEY_PATH" may be set in place of this flag. + --client-cert-path string Path to client cert to be verified by Confluent Manager for Apache Flink. Include for mTLS authentication. Environment variable "CONFLUENT_CMF_CLIENT_CERT_PATH" may be set in place of this flag. + --certificate-authority-path string Path to a PEM-encoded Certificate Authority to verify the Confluent Manager for Apache Flink connection. Environment variable "CONFLUENT_CMF_CERTIFICATE_AUTHORITY_PATH" may be set in place of this flag. + -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). diff --git a/test/fixtures/output/flink/secret/update-invalid-failure.golden b/test/fixtures/output/flink/secret/update-invalid-failure.golden new file mode 100644 index 0000000000..82447e41f3 --- /dev/null +++ b/test/fixtures/output/flink/secret/update-invalid-failure.golden @@ -0,0 +1 @@ +Error: failed to update secret "invalid-secret": The secret name is invalid diff --git a/test/fixtures/output/flink/secret/update-success-json.golden b/test/fixtures/output/flink/secret/update-success-json.golden new file mode 100644 index 0000000000..45f57f8883 --- /dev/null +++ b/test/fixtures/output/flink/secret/update-success-json.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "cmf/v1", + "kind": "Secret", + "metadata": { + "name": "test-secret", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "data": { + "bootstrap.servers": "****", + "sasl.jaas.config": "****" + } + } +} diff --git a/test/fixtures/output/flink/secret/update-success-yaml.golden b/test/fixtures/output/flink/secret/update-success-yaml.golden new file mode 100644 index 0000000000..0fc5d60dc5 --- /dev/null +++ b/test/fixtures/output/flink/secret/update-success-yaml.golden @@ -0,0 +1,9 @@ +apiVersion: cmf/v1 +kind: Secret +metadata: + name: test-secret + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC +spec: + data: + bootstrap.servers: '****' + sasl.jaas.config: '****' diff --git a/test/fixtures/output/flink/secret/update-success.golden b/test/fixtures/output/flink/secret/update-success.golden new file mode 100644 index 0000000000..8773d2cb62 --- /dev/null +++ b/test/fixtures/output/flink/secret/update-success.golden @@ -0,0 +1,4 @@ ++---------------+-------------------------------+ +| Creation Time | 2025-08-05 12:00:00 +0000 UTC | +| Name | test-secret | ++---------------+-------------------------------+ diff --git a/test/flink_onprem_test.go b/test/flink_onprem_test.go index 5536f0a14c..a8d5671086 100644 --- a/test/flink_onprem_test.go +++ b/test/flink_onprem_test.go @@ -682,6 +682,106 @@ func (s *CLITestSuite) TestFlinkShellOnPrem() { } }*/ +func (s *CLITestSuite) TestFlinkSecretCreateOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.json", fixture: "flink/secret/create-success.golden"}, + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.json --output json", fixture: "flink/secret/create-success-json.golden"}, + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.json --output yaml", fixture: "flink/secret/create-success-yaml.golden"}, + // failure + {args: "flink secret create test/fixtures/input/flink/secret/create-invalid-failure.json", fixture: "flink/secret/create-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretDeleteOnPrem() { + tests := []CLITest{ + // success scenarios + {args: "flink secret delete test-secret-1", input: "y\n", fixture: "flink/secret/delete-single-successful.golden"}, + {args: "flink secret delete test-secret-1 --force", fixture: "flink/secret/delete-single-force.golden"}, + // failure scenarios + {args: "flink secret delete non-exist-secret", input: "y\n", fixture: "flink/secret/delete-non-exist-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretDescribeOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret describe test-secret", fixture: "flink/secret/describe-success.golden"}, + {args: "flink secret describe test-secret --output json", fixture: "flink/secret/describe-success-json.golden"}, + {args: "flink secret describe test-secret --output yaml", fixture: "flink/secret/describe-success-yaml.golden"}, + // failure + {args: "flink secret describe invalid-secret", fixture: "flink/secret/describe-not-found.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretListOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret list", fixture: "flink/secret/list-success.golden"}, + {args: "flink secret list --output json", fixture: "flink/secret/list-success-json.golden"}, + {args: "flink secret list --output yaml", fixture: "flink/secret/list-success-yaml.golden"}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretUpdateOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.json", fixture: "flink/secret/update-success.golden"}, + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.json --output json", fixture: "flink/secret/update-success-json.golden"}, + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.json --output yaml", fixture: "flink/secret/update-success-yaml.golden"}, + // failure + {args: "flink secret update test/fixtures/input/flink/secret/update-invalid-failure.json", fixture: "flink/secret/update-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretCreateWithYAML() { + tests := []CLITest{ + // success scenarios with JSON files + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.json", fixture: "flink/secret/create-success.golden"}, + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.json --output json", fixture: "flink/secret/create-success-json.golden"}, + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.json --output yaml", fixture: "flink/secret/create-success-yaml.golden"}, + // failure scenarios with JSON files + {args: "flink secret create test/fixtures/input/flink/secret/create-invalid-failure.json", fixture: "flink/secret/create-invalid-failure.golden", exitCode: 1}, + // YAML file tests + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.yaml", fixture: "flink/secret/create-success.golden"}, + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.yaml --output json", fixture: "flink/secret/create-success-json.golden"}, + {args: "flink secret create test/fixtures/input/flink/secret/create-successful.yaml --output yaml", fixture: "flink/secret/create-success-yaml.golden"}, + // failure scenarios with YAML files + {args: "flink secret create test/fixtures/input/flink/secret/create-invalid-failure.yaml", fixture: "flink/secret/create-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretUpdateWithYAML() { + tests := []CLITest{ + // success scenarios with JSON files + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.json", fixture: "flink/secret/update-success.golden"}, + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.json --output json", fixture: "flink/secret/update-success-json.golden"}, + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.json --output yaml", fixture: "flink/secret/update-success-yaml.golden"}, + // failure scenarios with JSON files + {args: "flink secret update test/fixtures/input/flink/secret/update-invalid-failure.json", fixture: "flink/secret/update-invalid-failure.golden", exitCode: 1}, + // YAML file tests + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.yaml", fixture: "flink/secret/update-success.golden"}, + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.yaml --output json", fixture: "flink/secret/update-success-json.golden"}, + {args: "flink secret update test/fixtures/input/flink/secret/update-successful.yaml --output yaml", fixture: "flink/secret/update-success-yaml.golden"}, + // failure scenarios with YAML files + {args: "flink secret update test/fixtures/input/flink/secret/update-invalid-failure.yaml", fixture: "flink/secret/update-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + func (s *CLITestSuite) setupFlinkShellTestsOnPrem() { // Set the go-prompt file input env var, so go-prompt uses this file as the input stream err := os.Setenv(prompt.EnvVarInputFile, flinkShellInputStreamFile) diff --git a/test/test-server/flink_onprem_handler.go b/test/test-server/flink_onprem_handler.go index 3089876db8..0932b1f7c3 100644 --- a/test/test-server/flink_onprem_handler.go +++ b/test/test-server/flink_onprem_handler.go @@ -16,6 +16,8 @@ import ( cmfsdk "github.com/confluentinc/cmf-sdk-go/v1" ) +const invalidSecretName = "invalid-secret" + // Helper function to create a Flink application. func createApplication(name string) cmfsdk.FlinkApplication { status := map[string]interface{}{ @@ -1259,3 +1261,129 @@ func handleCmfStatementExceptions(t *testing.T) http.HandlerFunc { } } } + +func createSecret(secretName string) cmfsdk.Secret { + timeStamp := time.Date(2025, time.August, 5, 12, 0, 0, 0, time.UTC).String() + maskedData := map[string]string{ + "bootstrap.servers": "****", + "sasl.jaas.config": "****", + } + return cmfsdk.Secret{ + ApiVersion: "cmf/v1", + Kind: "Secret", + Metadata: cmfsdk.SecretMetadata{ + Name: secretName, + CreationTimestamp: &timeStamp, + }, + Spec: cmfsdk.SecretSpec{ + Data: &maskedData, + }, + } +} + +func handleCmfSecrets(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + handleLoginType(t, r) + switch r.Method { + case http.MethodGet: + secrets := []cmfsdk.Secret{ + createSecret("test-secret-1"), + createSecret("test-secret-2"), + } + secretsPage := cmfsdk.SecretsPage{} + page := r.URL.Query().Get("page") + + if page == "0" { + secretsPage.SetItems(secrets) + } + + err := json.NewEncoder(w).Encode(secretsPage) + require.NoError(t, err) + case http.MethodPost: + reqBody, err := io.ReadAll(r.Body) + require.NoError(t, err) + var secret cmfsdk.Secret + err = json.Unmarshal(reqBody, &secret) + require.NoError(t, err) + + secretName := secret.Metadata.Name + + if secretName == invalidSecretName { + http.Error(w, "The Secret object from resource file is invalid", http.StatusUnprocessableEntity) + return + } + + timeStamp := time.Date(2025, time.March, 12, 23, 42, 0, 0, time.UTC).String() + secret.Metadata.CreationTimestamp = &timeStamp + // Mask the secret data in response + maskedData := make(map[string]string) + if secret.Spec.Data != nil { + for k := range *secret.Spec.Data { + maskedData[k] = "****" + } + } + secret.Spec.Data = &maskedData + err = json.NewEncoder(w).Encode(secret) + require.NoError(t, err) + return + default: + require.Fail(t, fmt.Sprintf("Unexpected method %s", r.Method)) + } + } +} + +func handleCmfSecret(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + handleLoginType(t, r) + + vars := mux.Vars(r) + secretName := vars["secretName"] + + switch r.Method { + case http.MethodGet: + if secretName == invalidSecretName { + http.Error(w, "The secret name is invalid", http.StatusNotFound) + return + } + + secret := createSecret(secretName) + err := json.NewEncoder(w).Encode(secret) + require.NoError(t, err) + return + case http.MethodPut: + if secretName == invalidSecretName { + http.Error(w, "The secret name is invalid", http.StatusNotFound) + return + } + + reqBody, err := io.ReadAll(r.Body) + require.NoError(t, err) + var secret cmfsdk.Secret + err = json.Unmarshal(reqBody, &secret) + require.NoError(t, err) + + timeStamp := time.Date(2025, time.August, 5, 12, 0, 0, 0, time.UTC).String() + secret.Metadata.CreationTimestamp = &timeStamp + // Mask the secret data in response + maskedData := make(map[string]string) + if secret.Spec.Data != nil { + for k := range *secret.Spec.Data { + maskedData[k] = "****" + } + } + secret.Spec.Data = &maskedData + err = json.NewEncoder(w).Encode(secret) + require.NoError(t, err) + return + case http.MethodDelete: + if secretName == "non-exist-secret" { + http.Error(w, "", http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + return + default: + require.Fail(t, fmt.Sprintf("Unexpected method %s", r.Method)) + } + } +} diff --git a/test/test-server/flink_onprem_router.go b/test/test-server/flink_onprem_router.go index 2267f01d5a..f9a59af379 100644 --- a/test/test-server/flink_onprem_router.go +++ b/test/test-server/flink_onprem_router.go @@ -22,6 +22,8 @@ var flinkRoutes = []route{ {"/cmf/api/v1/environments/{envName}/statements/{stmtName}/savepoints", handleCmfSavepoints}, {"/cmf/api/v1/environments/{envName}/applications/{appName}/savepoints/{savepointName}", handleCmfSavepoint}, {"/cmf/api/v1/environments/{envName}/statements/{stmtName}/savepoints/{savepointName}", handleCmfSavepoint}, + {"/cmf/api/v1/secrets", handleCmfSecrets}, + {"/cmf/api/v1/secrets/{secretName}", handleCmfSecret}, {"/cmf/api/v1/detached-savepoints", handleCmfDetachedSavepoints}, {"/cmf/api/v1/detached-savepoints/{detachedSavepointName}", handleCmfDetachedSavepoint}, }