diff --git a/internal/flink/command.go b/internal/flink/command.go index b5cd215b75..feca0006f5 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.newSecretMappingCommand()) // On-Prem and Cloud Commands cmd.AddCommand(c.newComputePoolCommand(cfg)) diff --git a/internal/flink/command_secret_mapping.go b/internal/flink/command_secret_mapping.go new file mode 100644 index 0000000000..9b6f79040b --- /dev/null +++ b/internal/flink/command_secret_mapping.go @@ -0,0 +1,126 @@ +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 secretMappingOut struct { + CreationTime string `human:"Creation Time" serialized:"creation_time"` + Name string `human:"Name" serialized:"name"` + SecretName string `human:"Secret Name" serialized:"secret_name"` +} + +func (c *command) newSecretMappingCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "secret-mapping", + Short: "Manage Flink secret mappings.", + Long: "Manage Flink environment secret mappings in Confluent Platform.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogout}, + } + + cmd.AddCommand(c.newSecretMappingCreateCommand()) + cmd.AddCommand(c.newSecretMappingDeleteCommand()) + cmd.AddCommand(c.newSecretMappingDescribeCommand()) + cmd.AddCommand(c.newSecretMappingListCommand()) + cmd.AddCommand(c.newSecretMappingUpdateCommand()) + + return cmd +} + +func printSecretMappingOutput(cmd *cobra.Command, sdkMapping cmfsdk.EnvironmentSecretMapping) error { + if output.GetFormat(cmd) == output.Human { + table := output.NewTable(cmd) + var creationTime, name, secretName string + if sdkMapping.Metadata != nil { + if sdkMapping.Metadata.CreationTimestamp != nil { + creationTime = *sdkMapping.Metadata.CreationTimestamp + } + if sdkMapping.Metadata.Name != nil { + name = *sdkMapping.Metadata.Name + } + } + if sdkMapping.Spec != nil { + secretName = sdkMapping.Spec.SecretName + } + table.Add(&secretMappingOut{ + CreationTime: creationTime, + Name: name, + SecretName: secretName, + }) + return table.Print() + } + + localMapping := convertSdkSecretMappingToLocalSecretMapping(sdkMapping) + return output.SerializedOutput(cmd, localMapping) +} + +func readSecretMappingResourceFile(resourceFilePath string) (cmfsdk.EnvironmentSecretMapping, error) { + data, err := os.ReadFile(resourceFilePath) + if err != nil { + return cmfsdk.EnvironmentSecretMapping{}, 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.EnvironmentSecretMapping{}, errors.NewErrorWithSuggestions(fmt.Sprintf("unsupported file format: %s", ext), "Supported file formats are .json, .yaml, and .yml.") + } + if err != nil { + return cmfsdk.EnvironmentSecretMapping{}, fmt.Errorf("failed to parse input file: %w", err) + } + + jsonBytes, err := json.Marshal(genericData) + if err != nil { + return cmfsdk.EnvironmentSecretMapping{}, fmt.Errorf("failed to marshal intermediate data: %w", err) + } + + var sdkMapping cmfsdk.EnvironmentSecretMapping + if err = json.Unmarshal(jsonBytes, &sdkMapping); err != nil { + return cmfsdk.EnvironmentSecretMapping{}, fmt.Errorf("failed to bind data to EnvironmentSecretMapping model: %w", err) + } + + return sdkMapping, nil +} + +func convertSdkSecretMappingToLocalSecretMapping(sdkMapping cmfsdk.EnvironmentSecretMapping) LocalSecretMapping { + localMapping := LocalSecretMapping{ + ApiVersion: sdkMapping.ApiVersion, + Kind: sdkMapping.Kind, + } + + if sdkMapping.Metadata != nil { + localMapping.Metadata = LocalSecretMappingMetadata{ + Name: sdkMapping.Metadata.GetName(), + CreationTimestamp: sdkMapping.Metadata.CreationTimestamp, + UpdateTimestamp: sdkMapping.Metadata.UpdateTimestamp, + Uid: sdkMapping.Metadata.Uid, + Labels: sdkMapping.Metadata.Labels, + Annotations: sdkMapping.Metadata.Annotations, + } + } + + if sdkMapping.Spec != nil { + localMapping.Spec = LocalSecretMappingSpec{ + SecretName: sdkMapping.Spec.SecretName, + } + } + + return localMapping +} diff --git a/internal/flink/command_secret_mapping_create.go b/internal/flink/command_secret_mapping_create.go new file mode 100644 index 0000000000..3dc473608b --- /dev/null +++ b/internal/flink/command_secret_mapping_create.go @@ -0,0 +1,50 @@ +package flink + +import ( + "github.com/spf13/cobra" + + pcmd "github.com/confluentinc/cli/v4/pkg/cmd" +) + +func (c *command) newSecretMappingCreateCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "create ", + Short: "Create a Flink secret mapping.", + Long: "Create a Flink environment secret mapping in Confluent Platform.", + Args: cobra.ExactArgs(1), + RunE: c.secretMappingCreate, + } + + cmd.Flags().String("environment", "", "Name of the Flink environment.") + cobra.CheckErr(cmd.MarkFlagRequired("environment")) + addCmfFlagSet(cmd) + pcmd.AddOutputFlag(cmd) + + return cmd +} + +func (c *command) secretMappingCreate(cmd *cobra.Command, args []string) error { + resourceFilePath := args[0] + + environment, err := cmd.Flags().GetString("environment") + if err != nil { + return err + } + + client, err := c.GetCmfClient(cmd) + if err != nil { + return err + } + + sdkMapping, err := readSecretMappingResourceFile(resourceFilePath) + if err != nil { + return err + } + + sdkOutputMapping, err := client.CreateSecretMapping(c.createContext(), environment, sdkMapping) + if err != nil { + return err + } + + return printSecretMappingOutput(cmd, sdkOutputMapping) +} diff --git a/internal/flink/command_secret_mapping_delete.go b/internal/flink/command_secret_mapping_delete.go new file mode 100644 index 0000000000..a16821b403 --- /dev/null +++ b/internal/flink/command_secret_mapping_delete.go @@ -0,0 +1,57 @@ +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) newSecretMappingDeleteCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete [name-2] ... [name-n]", + Short: "Delete one or more Flink secret mappings.", + Long: "Delete one or more Flink environment secret mappings in Confluent Platform.", + Args: cobra.MinimumNArgs(1), + RunE: c.secretMappingDelete, + } + + cmd.Flags().String("environment", "", "Name of the Flink environment.") + cobra.CheckErr(cmd.MarkFlagRequired("environment")) + addCmfFlagSet(cmd) + pcmd.AddForceFlag(cmd) + + return cmd +} + +func (c *command) secretMappingDelete(cmd *cobra.Command, args []string) error { + environment, err := cmd.Flags().GetString("environment") + if err != nil { + return err + } + + client, err := c.GetCmfClient(cmd) + if err != nil { + return err + } + + existenceFunc := func(name string) bool { + _, err := client.DescribeSecretMapping(c.createContext(), environment, name) + return err == nil + } + + if err := deletion.ValidateAndConfirm(cmd, args, existenceFunc, resource.FlinkSecretMapping); err != nil { + suggestions := "List available Flink secret mappings with `confluent flink secret-mapping list --environment `." + suggestions += "\nCheck that CMF is running and accessible." + return errors.NewErrorWithSuggestions(err.Error(), suggestions) + } + + deleteFunc := func(name string) error { + return client.DeleteSecretMapping(c.createContext(), environment, name) + } + + _, err = deletion.Delete(cmd, args, deleteFunc, resource.FlinkSecretMapping) + return err +} diff --git a/internal/flink/command_secret_mapping_describe.go b/internal/flink/command_secret_mapping_describe.go new file mode 100644 index 0000000000..3dd6d37242 --- /dev/null +++ b/internal/flink/command_secret_mapping_describe.go @@ -0,0 +1,45 @@ +package flink + +import ( + "github.com/spf13/cobra" + + pcmd "github.com/confluentinc/cli/v4/pkg/cmd" +) + +func (c *command) newSecretMappingDescribeCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "describe ", + Short: "Describe a Flink secret mapping.", + Long: "Describe a Flink environment secret mapping in Confluent Platform.", + Args: cobra.ExactArgs(1), + RunE: c.secretMappingDescribe, + } + + cmd.Flags().String("environment", "", "Name of the Flink environment.") + cobra.CheckErr(cmd.MarkFlagRequired("environment")) + addCmfFlagSet(cmd) + pcmd.AddOutputFlag(cmd) + + return cmd +} + +func (c *command) secretMappingDescribe(cmd *cobra.Command, args []string) error { + name := args[0] + + environment, err := cmd.Flags().GetString("environment") + if err != nil { + return err + } + + client, err := c.GetCmfClient(cmd) + if err != nil { + return err + } + + sdkOutputMapping, err := client.DescribeSecretMapping(c.createContext(), environment, name) + if err != nil { + return err + } + + return printSecretMappingOutput(cmd, sdkOutputMapping) +} diff --git a/internal/flink/command_secret_mapping_list.go b/internal/flink/command_secret_mapping_list.go new file mode 100644 index 0000000000..98c9692699 --- /dev/null +++ b/internal/flink/command_secret_mapping_list.go @@ -0,0 +1,73 @@ +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) newSecretMappingListCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List Flink secret mappings.", + Long: "List Flink environment secret mappings in Confluent Platform.", + Args: cobra.NoArgs, + RunE: c.secretMappingList, + } + + cmd.Flags().String("environment", "", "Name of the Flink environment.") + cobra.CheckErr(cmd.MarkFlagRequired("environment")) + addCmfFlagSet(cmd) + pcmd.AddOutputFlag(cmd) + + return cmd +} + +func (c *command) secretMappingList(cmd *cobra.Command, _ []string) error { + environment, err := cmd.Flags().GetString("environment") + if err != nil { + return err + } + + client, err := c.GetCmfClient(cmd) + if err != nil { + return err + } + + sdkMappings, err := client.ListSecretMappings(c.createContext(), environment) + if err != nil { + return err + } + + if output.GetFormat(cmd) == output.Human { + list := output.NewList(cmd) + for _, mapping := range sdkMappings { + var creationTime, name, secretName string + if mapping.Metadata != nil { + if mapping.Metadata.CreationTimestamp != nil { + creationTime = *mapping.Metadata.CreationTimestamp + } + if mapping.Metadata.Name != nil { + name = *mapping.Metadata.Name + } + } + if mapping.Spec != nil { + secretName = mapping.Spec.SecretName + } + list.Add(&secretMappingOut{ + CreationTime: creationTime, + Name: name, + SecretName: secretName, + }) + } + return list.Print() + } + + localMappings := make([]LocalSecretMapping, 0, len(sdkMappings)) + for _, sdkMapping := range sdkMappings { + localMappings = append(localMappings, convertSdkSecretMappingToLocalSecretMapping(sdkMapping)) + } + + return output.SerializedOutput(cmd, localMappings) +} diff --git a/internal/flink/command_secret_mapping_update.go b/internal/flink/command_secret_mapping_update.go new file mode 100644 index 0000000000..4cf52f7541 --- /dev/null +++ b/internal/flink/command_secret_mapping_update.go @@ -0,0 +1,60 @@ +package flink + +import ( + "fmt" + + "github.com/spf13/cobra" + + pcmd "github.com/confluentinc/cli/v4/pkg/cmd" +) + +func (c *command) newSecretMappingUpdateCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "update ", + Short: "Update a Flink secret mapping.", + Long: "Update a Flink environment secret mapping in Confluent Platform.", + Args: cobra.ExactArgs(1), + RunE: c.secretMappingUpdate, + } + + cmd.Flags().String("environment", "", "Name of the Flink environment.") + cobra.CheckErr(cmd.MarkFlagRequired("environment")) + addCmfFlagSet(cmd) + pcmd.AddOutputFlag(cmd) + + return cmd +} + +func (c *command) secretMappingUpdate(cmd *cobra.Command, args []string) error { + resourceFilePath := args[0] + + environment, err := cmd.Flags().GetString("environment") + if err != nil { + return err + } + + client, err := c.GetCmfClient(cmd) + if err != nil { + return err + } + + sdkMapping, err := readSecretMappingResourceFile(resourceFilePath) + if err != nil { + return err + } + + var mappingName string + if sdkMapping.Metadata != nil && sdkMapping.Metadata.Name != nil { + mappingName = *sdkMapping.Metadata.Name + } + if mappingName == "" { + return fmt.Errorf(`secret mapping name is required: ensure the resource file contains a non-empty "metadata.name" field`) + } + + sdkOutputMapping, err := client.UpdateSecretMapping(c.createContext(), environment, mappingName, sdkMapping) + if err != nil { + return err + } + + return printSecretMappingOutput(cmd, sdkOutputMapping) +} diff --git a/internal/flink/local_types.go b/internal/flink/local_types.go index 19e0b756d9..e0d019cb0b 100644 --- a/internal/flink/local_types.go +++ b/internal/flink/local_types.go @@ -142,6 +142,26 @@ type LocalKafkaCatalogSpecSrInstance struct { ConnectionSecretId *string `json:"connectionSecretId,omitempty" yaml:"connectionSecretId,omitempty"` } +type LocalSecretMapping struct { + ApiVersion string `json:"apiVersion" yaml:"apiVersion"` + Kind string `json:"kind" yaml:"kind"` + Metadata LocalSecretMappingMetadata `json:"metadata" yaml:"metadata"` + Spec LocalSecretMappingSpec `json:"spec" yaml:"spec"` +} + +type LocalSecretMappingMetadata 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 LocalSecretMappingSpec struct { + SecretName string `json:"secretName" yaml:"secretName"` +} + 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..3296b93268 100644 --- a/pkg/flink/cmf_rest_client.go +++ b/pkg/flink/cmf_rest_client.go @@ -570,6 +570,57 @@ func (cmfClient *CmfRestClient) DeleteCatalog(ctx context.Context, catalogName s return parseSdkError(httpResp, err) } +func (cmfClient *CmfRestClient) CreateSecretMapping(ctx context.Context, envName string, secretMapping cmfsdk.EnvironmentSecretMapping) (cmfsdk.EnvironmentSecretMapping, error) { + var mappingName string + if secretMapping.Metadata != nil && secretMapping.Metadata.Name != nil { + mappingName = *secretMapping.Metadata.Name + } + outputMapping, httpResponse, err := cmfClient.EnvironmentsApi.CreateEnvironmentSecretMapping(ctx, envName).EnvironmentSecretMapping(secretMapping).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return cmfsdk.EnvironmentSecretMapping{}, fmt.Errorf(`failed to create secret mapping "%s" in the environment "%s": %s`, mappingName, envName, parsedErr) + } + return outputMapping, nil +} + +func (cmfClient *CmfRestClient) DescribeSecretMapping(ctx context.Context, envName, name string) (cmfsdk.EnvironmentSecretMapping, error) { + outputMapping, httpResponse, err := cmfClient.EnvironmentsApi.GetEnvironmentSecretMapping(ctx, envName, name).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return cmfsdk.EnvironmentSecretMapping{}, fmt.Errorf(`failed to get secret mapping "%s" in the environment "%s": %s`, name, envName, parsedErr) + } + return outputMapping, nil +} + +func (cmfClient *CmfRestClient) ListSecretMappings(ctx context.Context, envName string) ([]cmfsdk.EnvironmentSecretMapping, error) { + mappings := make([]cmfsdk.EnvironmentSecretMapping, 0) + done := false + const pageSize = 100 + var currentPageNumber int32 = 0 + + for !done { + mappingsPage, httpResponse, err := cmfClient.EnvironmentsApi.GetEnvironmentSecretMappings(ctx, envName).Page(currentPageNumber).Size(pageSize).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return nil, fmt.Errorf(`failed to list secret mappings in the environment "%s": %s`, envName, parsedErr) + } + mappings = append(mappings, mappingsPage.GetItems()...) + currentPageNumber, done = extractPageOptions(len(mappingsPage.GetItems()), currentPageNumber) + } + + return mappings, nil +} + +func (cmfClient *CmfRestClient) UpdateSecretMapping(ctx context.Context, envName, name string, secretMapping cmfsdk.EnvironmentSecretMapping) (cmfsdk.EnvironmentSecretMapping, error) { + outputMapping, httpResponse, err := cmfClient.EnvironmentsApi.UpdateEnvironmentSecretMapping(ctx, envName, name).EnvironmentSecretMapping(secretMapping).Execute() + if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil { + return cmfsdk.EnvironmentSecretMapping{}, fmt.Errorf(`failed to update secret mapping "%s" in the environment "%s": %s`, name, envName, parsedErr) + } + return outputMapping, nil +} + +func (cmfClient *CmfRestClient) DeleteSecretMapping(ctx context.Context, envName, name string) error { + httpResp, err := cmfClient.EnvironmentsApi.DeleteEnvironmentSecretMapping(ctx, envName, name).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..56cde8cc6f 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" + FlinkSecretMapping = "Flink secret mapping" Gateway = "gateway" IdentityPool = "identity pool" IdentityProvider = "identity provider" diff --git a/test/fixtures/input/flink/secret-mapping/create-invalid-failure.json b/test/fixtures/input/flink/secret-mapping/create-invalid-failure.json new file mode 100644 index 0000000000..0c3011777c --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/create-invalid-failure.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "invalid-secret-mapping" + }, + "spec": { + "secretName": "my-actual-secret" + } +} diff --git a/test/fixtures/input/flink/secret-mapping/create-invalid-failure.yaml b/test/fixtures/input/flink/secret-mapping/create-invalid-failure.yaml new file mode 100644 index 0000000000..6d89cc665f --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/create-invalid-failure.yaml @@ -0,0 +1,6 @@ +apiVersion: cmf/v1 +kind: EnvironmentSecretMapping +metadata: + name: invalid-secret-mapping +spec: + secretName: my-actual-secret diff --git a/test/fixtures/input/flink/secret-mapping/create-successful.json b/test/fixtures/input/flink/secret-mapping/create-successful.json new file mode 100644 index 0000000000..4b38516c0b --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/create-successful.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "test-mapping" + }, + "spec": { + "secretName": "my-actual-secret" + } +} diff --git a/test/fixtures/input/flink/secret-mapping/create-successful.yaml b/test/fixtures/input/flink/secret-mapping/create-successful.yaml new file mode 100644 index 0000000000..740597a8fd --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/create-successful.yaml @@ -0,0 +1,6 @@ +apiVersion: cmf/v1 +kind: EnvironmentSecretMapping +metadata: + name: test-mapping +spec: + secretName: my-actual-secret diff --git a/test/fixtures/input/flink/secret-mapping/update-invalid-failure.json b/test/fixtures/input/flink/secret-mapping/update-invalid-failure.json new file mode 100644 index 0000000000..ca8cac0e02 --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/update-invalid-failure.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "invalid-secret-mapping" + }, + "spec": { + "secretName": "my-updated-secret" + } +} diff --git a/test/fixtures/input/flink/secret-mapping/update-invalid-failure.yaml b/test/fixtures/input/flink/secret-mapping/update-invalid-failure.yaml new file mode 100644 index 0000000000..67a5b2ab6f --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/update-invalid-failure.yaml @@ -0,0 +1,6 @@ +apiVersion: cmf/v1 +kind: EnvironmentSecretMapping +metadata: + name: invalid-secret-mapping +spec: + secretName: my-updated-secret diff --git a/test/fixtures/input/flink/secret-mapping/update-successful.json b/test/fixtures/input/flink/secret-mapping/update-successful.json new file mode 100644 index 0000000000..aeb35190de --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/update-successful.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "test-mapping" + }, + "spec": { + "secretName": "my-updated-secret" + } +} diff --git a/test/fixtures/input/flink/secret-mapping/update-successful.yaml b/test/fixtures/input/flink/secret-mapping/update-successful.yaml new file mode 100644 index 0000000000..6c87fc0851 --- /dev/null +++ b/test/fixtures/input/flink/secret-mapping/update-successful.yaml @@ -0,0 +1,6 @@ +apiVersion: cmf/v1 +kind: EnvironmentSecretMapping +metadata: + name: test-mapping +spec: + secretName: my-updated-secret diff --git a/test/fixtures/output/flink/help-onprem.golden b/test/fixtures/output/flink/help-onprem.golden index 7a1902639d..e805d9de6b 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-mapping Manage Flink secret mappings. statement Manage Flink SQL statements. Global Flags: diff --git a/test/fixtures/output/flink/secret-mapping/create-help-onprem.golden b/test/fixtures/output/flink/secret-mapping/create-help-onprem.golden new file mode 100644 index 0000000000..d8a232d4cd --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/create-help-onprem.golden @@ -0,0 +1,17 @@ +Create a Flink environment secret mapping in Confluent Platform. + +Usage: + confluent flink secret-mapping create [flags] + +Flags: + --environment string REQUIRED: Name of the Flink environment. + --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-mapping/create-invalid-failure.golden b/test/fixtures/output/flink/secret-mapping/create-invalid-failure.golden new file mode 100644 index 0000000000..7d03e8b931 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/create-invalid-failure.golden @@ -0,0 +1 @@ +Error: failed to create secret mapping "invalid-secret-mapping" in the environment "test-env": The EnvironmentSecretMapping object from resource file is invalid diff --git a/test/fixtures/output/flink/secret-mapping/create-success-json.golden b/test/fixtures/output/flink/secret-mapping/create-success-json.golden new file mode 100644 index 0000000000..a62df22d86 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/create-success-json.golden @@ -0,0 +1,11 @@ +{ + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "test-mapping", + "creationTimestamp": "2025-03-12 23:42:00 +0000 UTC" + }, + "spec": { + "secretName": "my-actual-secret" + } +} diff --git a/test/fixtures/output/flink/secret-mapping/create-success-yaml.golden b/test/fixtures/output/flink/secret-mapping/create-success-yaml.golden new file mode 100644 index 0000000000..700ca3eb9d --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/create-success-yaml.golden @@ -0,0 +1,7 @@ +apiVersion: cmf/v1 +kind: EnvironmentSecretMapping +metadata: + name: test-mapping + creationTimestamp: 2025-03-12 23:42:00 +0000 UTC +spec: + secretName: my-actual-secret diff --git a/test/fixtures/output/flink/secret-mapping/create-success.golden b/test/fixtures/output/flink/secret-mapping/create-success.golden new file mode 100644 index 0000000000..1f8f2c2e02 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/create-success.golden @@ -0,0 +1,5 @@ ++---------------+-------------------------------+ +| Creation Time | 2025-03-12 23:42:00 +0000 UTC | +| Name | test-mapping | +| Secret Name | my-actual-secret | ++---------------+-------------------------------+ diff --git a/test/fixtures/output/flink/secret-mapping/delete-help-onprem.golden b/test/fixtures/output/flink/secret-mapping/delete-help-onprem.golden new file mode 100644 index 0000000000..4d9c1cabc0 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/delete-help-onprem.golden @@ -0,0 +1,17 @@ +Delete one or more Flink environment secret mappings in Confluent Platform. + +Usage: + confluent flink secret-mapping delete [name-2] ... [name-n] [flags] + +Flags: + --environment string REQUIRED: Name of the Flink environment. + --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-mapping/delete-non-exist-failure.golden b/test/fixtures/output/flink/secret-mapping/delete-non-exist-failure.golden new file mode 100644 index 0000000000..ffc87a736e --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/delete-non-exist-failure.golden @@ -0,0 +1 @@ +Are you sure you want to delete Flink secret mapping "non-exist-secret-mapping"? (y/n): Error: failed to delete non-exist-secret-mapping: 404 Not Found diff --git a/test/fixtures/output/flink/secret-mapping/delete-single-force.golden b/test/fixtures/output/flink/secret-mapping/delete-single-force.golden new file mode 100644 index 0000000000..997830e6ef --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/delete-single-force.golden @@ -0,0 +1 @@ +Deleted Flink secret mapping "test-mapping-1". diff --git a/test/fixtures/output/flink/secret-mapping/delete-single-successful.golden b/test/fixtures/output/flink/secret-mapping/delete-single-successful.golden new file mode 100644 index 0000000000..ac04b3d7e9 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/delete-single-successful.golden @@ -0,0 +1 @@ +Are you sure you want to delete Flink secret mapping "test-mapping-1"? (y/n): Deleted Flink secret mapping "test-mapping-1". diff --git a/test/fixtures/output/flink/secret-mapping/describe-help-onprem.golden b/test/fixtures/output/flink/secret-mapping/describe-help-onprem.golden new file mode 100644 index 0000000000..15e009ee8b --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/describe-help-onprem.golden @@ -0,0 +1,17 @@ +Describe a Flink environment secret mapping in Confluent Platform. + +Usage: + confluent flink secret-mapping describe [flags] + +Flags: + --environment string REQUIRED: Name of the Flink environment. + --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-mapping/describe-not-found.golden b/test/fixtures/output/flink/secret-mapping/describe-not-found.golden new file mode 100644 index 0000000000..2289e14a3f --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/describe-not-found.golden @@ -0,0 +1 @@ +Error: failed to get secret mapping "invalid-secret-mapping" in the environment "test-env": The secret mapping name is invalid diff --git a/test/fixtures/output/flink/secret-mapping/describe-success-json.golden b/test/fixtures/output/flink/secret-mapping/describe-success-json.golden new file mode 100644 index 0000000000..c68cb96478 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/describe-success-json.golden @@ -0,0 +1,11 @@ +{ + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "test-mapping", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "secretName": "my-actual-secret" + } +} diff --git a/test/fixtures/output/flink/secret-mapping/describe-success-yaml.golden b/test/fixtures/output/flink/secret-mapping/describe-success-yaml.golden new file mode 100644 index 0000000000..f1b57a807b --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/describe-success-yaml.golden @@ -0,0 +1,7 @@ +apiVersion: cmf/v1 +kind: EnvironmentSecretMapping +metadata: + name: test-mapping + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC +spec: + secretName: my-actual-secret diff --git a/test/fixtures/output/flink/secret-mapping/describe-success.golden b/test/fixtures/output/flink/secret-mapping/describe-success.golden new file mode 100644 index 0000000000..000bf07d24 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/describe-success.golden @@ -0,0 +1,5 @@ ++---------------+-------------------------------+ +| Creation Time | 2025-08-05 12:00:00 +0000 UTC | +| Name | test-mapping | +| Secret Name | my-actual-secret | ++---------------+-------------------------------+ diff --git a/test/fixtures/output/flink/secret-mapping/help-onprem.golden b/test/fixtures/output/flink/secret-mapping/help-onprem.golden new file mode 100644 index 0000000000..8d8d1b4ae5 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/help-onprem.golden @@ -0,0 +1,18 @@ +Manage Flink environment secret mappings in Confluent Platform. + +Usage: + confluent flink secret-mapping [command] + +Available Commands: + create Create a Flink secret mapping. + delete Delete one or more Flink secret mappings. + describe Describe a Flink secret mapping. + list List Flink secret mappings. + update Update a Flink secret mapping. + +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-mapping [command] --help" for more information about a command. diff --git a/test/fixtures/output/flink/secret-mapping/list-help-onprem.golden b/test/fixtures/output/flink/secret-mapping/list-help-onprem.golden new file mode 100644 index 0000000000..4cc47721ae --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/list-help-onprem.golden @@ -0,0 +1,17 @@ +List Flink environment secret mappings in Confluent Platform. + +Usage: + confluent flink secret-mapping list [flags] + +Flags: + --environment string REQUIRED: Name of the Flink environment. + --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-mapping/list-success-json.golden b/test/fixtures/output/flink/secret-mapping/list-success-json.golden new file mode 100644 index 0000000000..9eadacbcb6 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/list-success-json.golden @@ -0,0 +1,24 @@ +[ + { + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "test-mapping-1", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "secretName": "my-actual-secret" + } + }, + { + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "test-mapping-2", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "secretName": "my-actual-secret" + } + } +] diff --git a/test/fixtures/output/flink/secret-mapping/list-success-yaml.golden b/test/fixtures/output/flink/secret-mapping/list-success-yaml.golden new file mode 100644 index 0000000000..36972c4d7e --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/list-success-yaml.golden @@ -0,0 +1,14 @@ +- apiVersion: cmf/v1 + kind: EnvironmentSecretMapping + metadata: + name: test-mapping-1 + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC + spec: + secretName: my-actual-secret +- apiVersion: cmf/v1 + kind: EnvironmentSecretMapping + metadata: + name: test-mapping-2 + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC + spec: + secretName: my-actual-secret diff --git a/test/fixtures/output/flink/secret-mapping/list-success.golden b/test/fixtures/output/flink/secret-mapping/list-success.golden new file mode 100644 index 0000000000..3c0b96fa80 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/list-success.golden @@ -0,0 +1,4 @@ + Creation Time | Name | Secret Name +--------------------------------+----------------+------------------- + 2025-08-05 12:00:00 +0000 UTC | test-mapping-1 | my-actual-secret + 2025-08-05 12:00:00 +0000 UTC | test-mapping-2 | my-actual-secret diff --git a/test/fixtures/output/flink/secret-mapping/update-help-onprem.golden b/test/fixtures/output/flink/secret-mapping/update-help-onprem.golden new file mode 100644 index 0000000000..e5a20b87cb --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/update-help-onprem.golden @@ -0,0 +1,17 @@ +Update a Flink environment secret mapping in Confluent Platform. + +Usage: + confluent flink secret-mapping update [flags] + +Flags: + --environment string REQUIRED: Name of the Flink environment. + --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-mapping/update-invalid-failure.golden b/test/fixtures/output/flink/secret-mapping/update-invalid-failure.golden new file mode 100644 index 0000000000..fd3872faed --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/update-invalid-failure.golden @@ -0,0 +1 @@ +Error: failed to update secret mapping "invalid-secret-mapping" in the environment "test-env": The secret mapping name is invalid diff --git a/test/fixtures/output/flink/secret-mapping/update-success-json.golden b/test/fixtures/output/flink/secret-mapping/update-success-json.golden new file mode 100644 index 0000000000..dfe760325e --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/update-success-json.golden @@ -0,0 +1,11 @@ +{ + "apiVersion": "cmf/v1", + "kind": "EnvironmentSecretMapping", + "metadata": { + "name": "test-mapping", + "creationTimestamp": "2025-08-05 12:00:00 +0000 UTC" + }, + "spec": { + "secretName": "my-updated-secret" + } +} diff --git a/test/fixtures/output/flink/secret-mapping/update-success-yaml.golden b/test/fixtures/output/flink/secret-mapping/update-success-yaml.golden new file mode 100644 index 0000000000..d2a63b1d2b --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/update-success-yaml.golden @@ -0,0 +1,7 @@ +apiVersion: cmf/v1 +kind: EnvironmentSecretMapping +metadata: + name: test-mapping + creationTimestamp: 2025-08-05 12:00:00 +0000 UTC +spec: + secretName: my-updated-secret diff --git a/test/fixtures/output/flink/secret-mapping/update-success.golden b/test/fixtures/output/flink/secret-mapping/update-success.golden new file mode 100644 index 0000000000..74c79409a5 --- /dev/null +++ b/test/fixtures/output/flink/secret-mapping/update-success.golden @@ -0,0 +1,5 @@ ++---------------+-------------------------------+ +| Creation Time | 2025-08-05 12:00:00 +0000 UTC | +| Name | test-mapping | +| Secret Name | my-updated-secret | ++---------------+-------------------------------+ diff --git a/test/flink_onprem_test.go b/test/flink_onprem_test.go index 5536f0a14c..a7835a1479 100644 --- a/test/flink_onprem_test.go +++ b/test/flink_onprem_test.go @@ -607,6 +607,105 @@ func (s *CLITestSuite) TestFlinkCatalogCreateWithYAML() { runIntegrationTestsWithMultipleAuth(s, tests) } +func (s *CLITestSuite) TestFlinkSecretMappingCreateOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.json --environment test-env", fixture: "flink/secret-mapping/create-success.golden"}, + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.json --environment test-env --output json", fixture: "flink/secret-mapping/create-success-json.golden"}, + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.json --environment test-env --output yaml", fixture: "flink/secret-mapping/create-success-yaml.golden"}, + // failure + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-invalid-failure.json --environment test-env", fixture: "flink/secret-mapping/create-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretMappingDeleteOnPrem() { + tests := []CLITest{ + // success scenarios + {args: "flink secret-mapping delete test-mapping-1 --environment test-env", input: "y\n", fixture: "flink/secret-mapping/delete-single-successful.golden"}, + {args: "flink secret-mapping delete test-mapping-1 --environment test-env --force", fixture: "flink/secret-mapping/delete-single-force.golden"}, + // failure scenarios + {args: "flink secret-mapping delete non-exist-secret-mapping --environment test-env", input: "y\n", fixture: "flink/secret-mapping/delete-non-exist-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretMappingDescribeOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret-mapping describe test-mapping --environment test-env", fixture: "flink/secret-mapping/describe-success.golden"}, + {args: "flink secret-mapping describe test-mapping --environment test-env --output json", fixture: "flink/secret-mapping/describe-success-json.golden"}, + {args: "flink secret-mapping describe test-mapping --environment test-env --output yaml", fixture: "flink/secret-mapping/describe-success-yaml.golden"}, + // failure + {args: "flink secret-mapping describe invalid-secret-mapping --environment test-env", fixture: "flink/secret-mapping/describe-not-found.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretMappingListOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret-mapping list --environment test-env", fixture: "flink/secret-mapping/list-success.golden"}, + {args: "flink secret-mapping list --environment test-env --output json", fixture: "flink/secret-mapping/list-success-json.golden"}, + {args: "flink secret-mapping list --environment test-env --output yaml", fixture: "flink/secret-mapping/list-success-yaml.golden"}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretMappingUpdateOnPrem() { + tests := []CLITest{ + // success + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.json --environment test-env", fixture: "flink/secret-mapping/update-success.golden"}, + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.json --environment test-env --output json", fixture: "flink/secret-mapping/update-success-json.golden"}, + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.json --environment test-env --output yaml", fixture: "flink/secret-mapping/update-success-yaml.golden"}, + // failure + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-invalid-failure.json --environment test-env", fixture: "flink/secret-mapping/update-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretMappingCreateWithYAML() { + tests := []CLITest{ + // success scenarios with JSON files + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.json --environment test-env", fixture: "flink/secret-mapping/create-success.golden"}, + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.json --environment test-env --output json", fixture: "flink/secret-mapping/create-success-json.golden"}, + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.json --environment test-env --output yaml", fixture: "flink/secret-mapping/create-success-yaml.golden"}, + // failure scenarios with JSON files + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-invalid-failure.json --environment test-env", fixture: "flink/secret-mapping/create-invalid-failure.golden", exitCode: 1}, + // YAML file tests + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.yaml --environment test-env", fixture: "flink/secret-mapping/create-success.golden"}, + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.yaml --environment test-env --output json", fixture: "flink/secret-mapping/create-success-json.golden"}, + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-successful.yaml --environment test-env --output yaml", fixture: "flink/secret-mapping/create-success-yaml.golden"}, + // failure scenarios with YAML files + {args: "flink secret-mapping create test/fixtures/input/flink/secret-mapping/create-invalid-failure.yaml --environment test-env", fixture: "flink/secret-mapping/create-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} + +func (s *CLITestSuite) TestFlinkSecretMappingUpdateWithYAML() { + tests := []CLITest{ + // success scenarios with JSON files + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.json --environment test-env", fixture: "flink/secret-mapping/update-success.golden"}, + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.json --environment test-env --output json", fixture: "flink/secret-mapping/update-success-json.golden"}, + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.json --environment test-env --output yaml", fixture: "flink/secret-mapping/update-success-yaml.golden"}, + // failure scenarios with JSON files + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-invalid-failure.json --environment test-env", fixture: "flink/secret-mapping/update-invalid-failure.golden", exitCode: 1}, + // YAML file tests + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.yaml --environment test-env", fixture: "flink/secret-mapping/update-success.golden"}, + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.yaml --environment test-env --output json", fixture: "flink/secret-mapping/update-success-json.golden"}, + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-successful.yaml --environment test-env --output yaml", fixture: "flink/secret-mapping/update-success-yaml.golden"}, + // failure scenarios with YAML files + {args: "flink secret-mapping update test/fixtures/input/flink/secret-mapping/update-invalid-failure.yaml --environment test-env", fixture: "flink/secret-mapping/update-invalid-failure.golden", exitCode: 1}, + } + + runIntegrationTestsWithMultipleAuth(s, tests) +} func (s *CLITestSuite) TestFlinkShellOnPrem() { tests := []flinkShellTest{ { diff --git a/test/test-server/flink_onprem_handler.go b/test/test-server/flink_onprem_handler.go index 3089876db8..2055ba5d1f 100644 --- a/test/test-server/flink_onprem_handler.go +++ b/test/test-server/flink_onprem_handler.go @@ -16,6 +16,25 @@ import ( cmfsdk "github.com/confluentinc/cmf-sdk-go/v1" ) +const invalidSecretMappingName = "invalid-secret-mapping" + +func createSecretMapping(name string) cmfsdk.EnvironmentSecretMapping { + timeStamp := time.Date(2025, time.August, 5, 12, 0, 0, 0, time.UTC).String() + mappingName := name + secretName := "my-actual-secret" + return cmfsdk.EnvironmentSecretMapping{ + ApiVersion: "cmf/v1", + Kind: "EnvironmentSecretMapping", + Metadata: &cmfsdk.EnvironmentSecretMappingMetadata{ + Name: &mappingName, + CreationTimestamp: &timeStamp, + }, + Spec: &cmfsdk.EnvironmentSecretMappingSpec{ + SecretName: secretName, + }, + } +} + // Helper function to create a Flink application. func createApplication(name string) cmfsdk.FlinkApplication { status := map[string]interface{}{ @@ -1259,3 +1278,103 @@ func handleCmfStatementExceptions(t *testing.T) http.HandlerFunc { } } } + +func handleCmfSecretMappings(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + handleLoginType(t, r) + switch r.Method { + case http.MethodGet: + mappings := []cmfsdk.EnvironmentSecretMapping{ + createSecretMapping("test-mapping-1"), + createSecretMapping("test-mapping-2"), + } + mappingsPage := cmfsdk.EnvironmentSecretMappingsPage{} + page := r.URL.Query().Get("page") + + if page == "0" { + mappingsPage.SetItems(mappings) + } + + err := json.NewEncoder(w).Encode(mappingsPage) + require.NoError(t, err) + case http.MethodPost: + reqBody, err := io.ReadAll(r.Body) + require.NoError(t, err) + var mapping cmfsdk.EnvironmentSecretMapping + err = json.Unmarshal(reqBody, &mapping) + require.NoError(t, err) + + var mappingName string + if mapping.Metadata != nil && mapping.Metadata.Name != nil { + mappingName = *mapping.Metadata.Name + } + + if mappingName == invalidSecretMappingName { + http.Error(w, "The EnvironmentSecretMapping object from resource file is invalid", http.StatusUnprocessableEntity) + return + } + + timeStamp := time.Date(2025, time.March, 12, 23, 42, 0, 0, time.UTC).String() + if mapping.Metadata == nil { + mapping.Metadata = &cmfsdk.EnvironmentSecretMappingMetadata{} + } + mapping.Metadata.CreationTimestamp = &timeStamp + err = json.NewEncoder(w).Encode(mapping) + require.NoError(t, err) + return + default: + require.Fail(t, fmt.Sprintf("Unexpected method %s", r.Method)) + } + } +} + +func handleCmfSecretMapping(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + handleLoginType(t, r) + + vars := mux.Vars(r) + name := vars["name"] + + switch r.Method { + case http.MethodGet: + if name == invalidSecretMappingName { + http.Error(w, "The secret mapping name is invalid", http.StatusNotFound) + return + } + + mapping := createSecretMapping(name) + err := json.NewEncoder(w).Encode(mapping) + require.NoError(t, err) + return + case http.MethodPut: + if name == invalidSecretMappingName { + http.Error(w, "The secret mapping name is invalid", http.StatusNotFound) + return + } + + reqBody, err := io.ReadAll(r.Body) + require.NoError(t, err) + var mapping cmfsdk.EnvironmentSecretMapping + err = json.Unmarshal(reqBody, &mapping) + require.NoError(t, err) + + timeStamp := time.Date(2025, time.August, 5, 12, 0, 0, 0, time.UTC).String() + if mapping.Metadata == nil { + mapping.Metadata = &cmfsdk.EnvironmentSecretMappingMetadata{} + } + mapping.Metadata.CreationTimestamp = &timeStamp + err = json.NewEncoder(w).Encode(mapping) + require.NoError(t, err) + return + case http.MethodDelete: + if name == "non-exist-secret-mapping" { + 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..a9093e7ff2 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/environments/{envName}/secret-mappings", handleCmfSecretMappings}, + {"/cmf/api/v1/environments/{envName}/secret-mappings/{name}", handleCmfSecretMapping}, {"/cmf/api/v1/detached-savepoints", handleCmfDetachedSavepoints}, {"/cmf/api/v1/detached-savepoints/{detachedSavepointName}", handleCmfDetachedSavepoint}, }