Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion internal/flink/command_application_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ package flink

import (
"fmt"
"slices"
"strings"

"github.com/spf13/cobra"

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

// allowedApplicationStatuses lists the Flink job states recognized by the CMF applications
// "state=" filter, per the cmf-sdk-go GetApplications filter documentation. Unknown values are
// still forwarded (the server returns no matches rather than erroring); this list only drives
// the advisory --status warning.
var allowedApplicationStatuses = []string{"RUNNING", "FINISHED", "FAILED", "CANCELED", "RECONCILING", "COMPLETED", "UNKNOWN"}

func (c *command) newApplicationListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Expand All @@ -18,6 +27,8 @@ func (c *command) newApplicationListCommand() *cobra.Command {
}

cmd.Flags().String("environment", "", "Name of the Flink environment.")
cmd.Flags().String("name", "", `Filter the Flink applications by name. Supports wildcards, for example "my-app*".`)
cmd.Flags().String("status", "", "Filter the Flink applications by status.")
addPageSizeFlag(cmd)
addCmfFlagSet(cmd)
pcmd.AddOutputFlag(cmd)
Expand All @@ -33,6 +44,22 @@ func (c *command) applicationList(cmd *cobra.Command, _ []string) error {
return err
}

name, err := cmd.Flags().GetString("name")
if err != nil {
return err
}

status, err := cmd.Flags().GetString("status")
if err != nil {
return err
}
if status != "" {
status = strings.ToUpper(status)
if !slices.Contains(allowedApplicationStatuses, status) {
output.ErrPrintf(c.Config.EnableColor, "[WARN] Invalid status %q. Valid statuses are %s.\n", status, utils.ArrayToCommaDelimitedString(allowedApplicationStatuses, "and"))
}
}

pageSize, err := getPageSize(cmd)
if err != nil {
return err
Expand All @@ -43,7 +70,7 @@ func (c *command) applicationList(cmd *cobra.Command, _ []string) error {
return err
}

applications, err := client.ListApplications(c.createContext(), environment, pageSize)
applications, err := client.ListApplications(c.createContext(), environment, buildApplicationFilter(name, status), pageSize)
if err != nil {
return err
}
Expand Down Expand Up @@ -82,3 +109,19 @@ func (c *command) applicationList(cmd *cobra.Command, _ []string) error {

return output.SerializedOutput(cmd, localApps)
}

// buildApplicationFilter composes the CMF applications "filter" query from the user-facing
// --name and --status flags. The grammar (comma-separated "key=value" expressions, "name="
// with an optional "*" suffix wildcard, and "state=" for status) follows the CMF applications
// list API. Values are not escaped: Kubernetes application names and Flink states cannot
// contain "," or "=", so no ambiguity arises. The caller normalizes and warns about status.
func buildApplicationFilter(name, status string) string {
filters := make([]string, 0, 2)
if name != "" {
filters = append(filters, fmt.Sprintf("name=%s", name))
}
if status != "" {
filters = append(filters, fmt.Sprintf("state=%s", status))
}
return strings.Join(filters, ",")
}
28 changes: 28 additions & 0 deletions internal/flink/command_application_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package flink

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestBuildApplicationFilter(t *testing.T) {
tests := []struct {
name string
appn string
status string
want string
}{
{name: "empty", appn: "", status: "", want: ""},
{name: "name only", appn: "my-app", status: "", want: "name=my-app"},
{name: "name wildcard", appn: "my-app*", status: "", want: "name=my-app*"},
{name: "status only", appn: "", status: "RUNNING", want: "state=RUNNING"},
{name: "name and status", appn: "a*", status: "RUNNING", want: "name=a*,state=RUNNING"},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.want, buildApplicationFilter(test.appn, test.status))
})
}
}
9 changes: 7 additions & 2 deletions pkg/flink/cmf_rest_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,14 @@ func (cmfClient *CmfRestClient) DescribeApplication(ctx context.Context, environ
return cmfApplication, nil
}

func (cmfClient *CmfRestClient) ListApplications(ctx context.Context, environment string, pageSize int32) ([]cmfsdk.FlinkApplication, error) {
func (cmfClient *CmfRestClient) ListApplications(ctx context.Context, environment, filter string, pageSize int32) ([]cmfsdk.FlinkApplication, error) {
request := cmfClient.FlinkApplicationsApi.GetApplications(ctx, environment)
if filter != "" {
request = request.Filter(filter)
}

return listAllPages(pageSize, func(page, size int32) ([]cmfsdk.FlinkApplication, error) {
applicationsPage, httpResponse, err := cmfClient.FlinkApplicationsApi.GetApplications(ctx, environment).Page(page).Size(size).Execute()
applicationsPage, httpResponse, err := request.Page(page).Size(size).Execute()
if parsedErr := parseSdkError(httpResponse, err); parsedErr != nil {
return nil, fmt.Errorf(`failed to list applications in the environment "%s": %s`, environment, parsedErr)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Usage:

Flags:
--environment string REQUIRED: Name of the Flink environment.
--name string Filter the Flink applications by name. Supports wildcards, for example "my-app*".
--status string Filter the Flink applications by status.
--page-size int Number of results to fetch per API request. Defaults to 100.
--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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Usage:

Flags:
--environment string REQUIRED: Name of the Flink environment.
--name string Filter the Flink applications by name. Supports wildcards, for example "my-app*".
--status string Filter the Flink applications by status.
--page-size int Number of results to fetch per API request. Defaults to 100.
--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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[
{
"apiVersion": "cmf.confluent.io/v1",
"kind": "FlinkApplication",
"metadata": {
"name": "default-application-s"
},
"spec": {
"flinkConfiguration": {
"metrics.reporter.prom.factory.class": "org.apache.flink.metrics.prometheus.PrometheusReporterFactory",
"metrics.reporter.prom.port": "9249-9250",
"taskmanager.numberOfTaskSlots": "8"
},
"flinkVersion": "v1_19",
"image": "confluentinc/cp-flink:1.19.1-cp1",
"job": {
"jarURI": "local:///opt/flink/examples/streaming/StateMachineExample.jar",
"parallelism": 3,
"state": "running",
"upgradeMode": "stateless"
},
"jobManager": {
"resource": {
"cpu": 1,
"memory": "1048m"
}
},
"serviceAccount": "flink",
"taskManager": {
"resource": {
"cpu": 1,
"memory": "1048m"
}
}
},
"status": {
"clusterInfo": {
"flink-revision": "89d0b8f @ 2024-06-22T13:19:31+02:00",
"flink-version": "1.19.1-cp1",
"total-cpu": "3.0",
"total-memory": "3296722944"
},
"error": null,
"jobManagerDeploymentStatus": "DEPLOYING",
"jobStatus": {
"checkpointInfo": {
"formatType": null,
"lastCheckpoint": null,
"lastPeriodicCheckpointTimestamp": 0,
"triggerId": null,
"triggerTimestamp": null,
"triggerType": null
},
"jobId": "dcabb1ad6c40495bc2d7fa7a0097c5aa",
"jobName": "State machine job",
"savepointInfo": {
"formatType": null,
"lastPeriodicSavepointTimestamp": 0,
"lastSavepoint": null,
"savepointHistory": [],
"triggerId": null,
"triggerTimestamp": null,
"triggerType": null
},
"startTime": "1726640263746",
"state": "RECONCILING",
"updateTime": "1726640280561"
},
"lifecycleState": "DEPLOYED",
"observedGeneration": 4,
"reconciliationStatus": {
"lastReconciledSpec": "",
"lastStableSpec": "",
"reconciliationTimestamp": 1726640346899,
"state": "DEPLOYED"
},
"taskManager": {
"labelSelector": "component=taskmanager,app=basic-example",
"replicas": 1
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[
{
"apiVersion": "cmf.confluent.io/v1",
"kind": "FlinkApplication",
"metadata": {
"name": "default-application-1"
},
"spec": {
"flinkConfiguration": {
"metrics.reporter.prom.factory.class": "org.apache.flink.metrics.prometheus.PrometheusReporterFactory",
"metrics.reporter.prom.port": "9249-9250",
"taskmanager.numberOfTaskSlots": "8"
},
"flinkVersion": "v1_19",
"image": "confluentinc/cp-flink:1.19.1-cp1",
"job": {
"jarURI": "local:///opt/flink/examples/streaming/StateMachineExample.jar",
"parallelism": 3,
"state": "running",
"upgradeMode": "stateless"
},
"jobManager": {
"resource": {
"cpu": 1,
"memory": "1048m"
}
},
"serviceAccount": "flink",
"taskManager": {
"resource": {
"cpu": 1,
"memory": "1048m"
}
}
},
"status": {
"clusterInfo": {
"flink-revision": "89d0b8f @ 2024-06-22T13:19:31+02:00",
"flink-version": "1.19.1-cp1",
"total-cpu": "3.0",
"total-memory": "3296722944"
},
"error": null,
"jobManagerDeploymentStatus": "DEPLOYING",
"jobStatus": {
"checkpointInfo": {
"formatType": null,
"lastCheckpoint": null,
"lastPeriodicCheckpointTimestamp": 0,
"triggerId": null,
"triggerTimestamp": null,
"triggerType": null
},
"jobId": "dcabb1ad6c40495bc2d7fa7a0097c5aa",
"jobName": "State machine job",
"savepointInfo": {
"formatType": null,
"lastPeriodicSavepointTimestamp": 0,
"lastSavepoint": null,
"savepointHistory": [],
"triggerId": null,
"triggerTimestamp": null,
"triggerType": null
},
"startTime": "1726640263746",
"state": "RECONCILING",
"updateTime": "1726640280561"
},
"lifecycleState": "DEPLOYED",
"observedGeneration": 4,
"reconciliationStatus": {
"lastReconciledSpec": "",
"lastStableSpec": "",
"reconciliationTimestamp": 1726640346899,
"state": "DEPLOYED"
},
"taskManager": {
"labelSelector": "component=taskmanager,app=basic-example",
"replicas": 1
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[
{
"apiVersion": "cmf.confluent.io/v1",
"kind": "FlinkApplication",
"metadata": {
"name": "default-application-1"
},
"spec": {
"flinkConfiguration": {
"metrics.reporter.prom.factory.class": "org.apache.flink.metrics.prometheus.PrometheusReporterFactory",
"metrics.reporter.prom.port": "9249-9250",
"taskmanager.numberOfTaskSlots": "8"
},
"flinkVersion": "v1_19",
"image": "confluentinc/cp-flink:1.19.1-cp1",
"job": {
"jarURI": "local:///opt/flink/examples/streaming/StateMachineExample.jar",
"parallelism": 3,
"state": "running",
"upgradeMode": "stateless"
},
"jobManager": {
"resource": {
"cpu": 1,
"memory": "1048m"
}
},
"serviceAccount": "flink",
"taskManager": {
"resource": {
"cpu": 1,
"memory": "1048m"
}
}
},
"status": {
"clusterInfo": {
"flink-revision": "89d0b8f @ 2024-06-22T13:19:31+02:00",
"flink-version": "1.19.1-cp1",
"total-cpu": "3.0",
"total-memory": "3296722944"
},
"error": null,
"jobManagerDeploymentStatus": "DEPLOYING",
"jobStatus": {
"checkpointInfo": {
"formatType": null,
"lastCheckpoint": null,
"lastPeriodicCheckpointTimestamp": 0,
"triggerId": null,
"triggerTimestamp": null,
"triggerType": null
},
"jobId": "dcabb1ad6c40495bc2d7fa7a0097c5aa",
"jobName": "State machine job",
"savepointInfo": {
"formatType": null,
"lastPeriodicSavepointTimestamp": 0,
"lastSavepoint": null,
"savepointHistory": [],
"triggerId": null,
"triggerTimestamp": null,
"triggerType": null
},
"startTime": "1726640263746",
"state": "RECONCILING",
"updateTime": "1726640280561"
},
"lifecycleState": "DEPLOYED",
"observedGeneration": 4,
"reconciliationStatus": {
"lastReconciledSpec": "",
"lastStableSpec": "",
"reconciliationTimestamp": 1726640346899,
"state": "DEPLOYED"
},
"taskManager": {
"labelSelector": "component=taskmanager,app=basic-example",
"replicas": 1
}
}
}
]
Loading