Skip to content

Commit 4c05c17

Browse files
kernel-internal[bot]cursoragentrgarciaNeil Dcruze
authored
Update Go SDK and add deploy/app delete commands (#118)
## Summary This PR updates the Go SDK dependency to the latest version and updates CLI commands to match SDK interface changes. ## SDK Update - Updated kernel-go-sdk to v0.37.0 (cb6bef95524033ec889ef9dc2eb8a2c43507ba36) ## Coverage Analysis A full enumeration of SDK methods and CLI commands was performed. The SDK update included a breaking change to the `ProfileService.List` method signature. ## Changes - **ProfileService.List**: Updated to accept `ProfileListParams` parameter (breaking change from SDK) - Added `--limit`, `--offset`, `--query` flags to `kernel profiles list` command - Updated ProfilesService interface and tests ## New Flags - `--limit` for `ProfileListParams.Limit` on `kernel profiles list` - `--offset` for `ProfileListParams.Offset` on `kernel profiles list` - `--query` for `ProfileListParams.Query` on `kernel profiles list` Triggered by: kernel/kernel-go-sdk@cb6bef9 Reviewer: @dcruzeneil2 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces new deletion flows (including concurrent deletes) that can remove deployments/apps, so correctness of filtering/confirmation and error handling is important despite relatively localized CLI changes. > > **Overview** > Updates `kernel-go-sdk` to `v0.37.0` (and adds `golang.org/x/sync`) and adapts the CLI to the SDK’s breaking `Profiles.List` signature by introducing paginated, queryable `kernel profiles list` (`--page`, `--per-page`, `--query`) with updated tests. > > Adds destructive-management commands: `kernel app delete <app_name>` (optionally `--version`, with confirmation and concurrent deletion of all matching deployments) and `kernel deploy delete <deployment_id>` (with confirmation/`--yes` and not-found handling). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 74ca985. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: kernel-internal[bot] <260533166+kernel-internal[bot]@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Rafael Garcia <raf@kernel.sh> Co-authored-by: Neil Dcruze <neildcruze@Neils-MacBook-Pro-203.local>
1 parent 2ce4c7e commit 4c05c17

File tree

6 files changed

+255
-21
lines changed

6 files changed

+255
-21
lines changed

cmd/app.go

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/pterm/pterm"
1010
"github.com/samber/lo"
1111
"github.com/spf13/cobra"
12+
"golang.org/x/sync/errgroup"
1213
)
1314

1415
var appCmd = &cobra.Command{
@@ -25,7 +26,14 @@ var appListCmd = &cobra.Command{
2526
RunE: runAppList,
2627
}
2728

28-
// --- app history subcommand (scaffold)
29+
var appDeleteCmd = &cobra.Command{
30+
Use: "delete <app_name>",
31+
Short: "Delete an app and all its deployments",
32+
Long: "Deletes all deployments for an application. Use --version to scope deletion to a specific version.",
33+
Args: cobra.ExactArgs(1),
34+
RunE: runAppDelete,
35+
}
36+
2937
var appHistoryCmd = &cobra.Command{
3038
Use: "history <app_name>",
3139
Short: "Show deployment history for an application",
@@ -36,8 +44,13 @@ var appHistoryCmd = &cobra.Command{
3644
func init() {
3745
// register subcommands under app
3846
appCmd.AddCommand(appListCmd)
47+
appCmd.AddCommand(appDeleteCmd)
3948
appCmd.AddCommand(appHistoryCmd)
4049

50+
// Flags for delete
51+
appDeleteCmd.Flags().String("version", "", "Only delete deployments for this version (default: all versions)")
52+
appDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
53+
4154
// Add optional filters for list
4255
appListCmd.Flags().String("name", "", "Filter by application name")
4356
appListCmd.Flags().String("version", "", "Filter by version label")
@@ -206,6 +219,76 @@ func runAppList(cmd *cobra.Command, args []string) error {
206219
return nil
207220
}
208221

222+
func runAppDelete(cmd *cobra.Command, args []string) error {
223+
client := getKernelClient(cmd)
224+
appName := args[0]
225+
version, _ := cmd.Flags().GetString("version")
226+
skipConfirm, _ := cmd.Flags().GetBool("yes")
227+
228+
params := kernel.DeploymentListParams{
229+
AppName: kernel.Opt(appName),
230+
Limit: kernel.Opt(int64(100)),
231+
Offset: kernel.Opt(int64(0)),
232+
}
233+
if version != "" {
234+
params.AppVersion = kernel.Opt(version)
235+
}
236+
237+
initial, err := client.Deployments.List(cmd.Context(), params)
238+
if err != nil {
239+
return util.CleanedUpSdkError{Err: err}
240+
}
241+
if initial == nil || len(initial.Items) == 0 {
242+
pterm.Info.Printf("No deployments found for app '%s'\n", appName)
243+
return nil
244+
}
245+
246+
if !skipConfirm {
247+
scope := "all versions"
248+
if version != "" {
249+
scope = fmt.Sprintf("version '%s'", version)
250+
}
251+
msg := fmt.Sprintf("Delete all deployments for app '%s' (%s)? This cannot be undone.", appName, scope)
252+
pterm.DefaultInteractiveConfirm.DefaultText = msg
253+
ok, _ := pterm.DefaultInteractiveConfirm.Show()
254+
if !ok {
255+
pterm.Info.Println("Deletion cancelled")
256+
return nil
257+
}
258+
}
259+
260+
spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Deleting deployments for app '%s'...", appName))
261+
deleted := 0
262+
263+
for {
264+
page, err := client.Deployments.List(cmd.Context(), params)
265+
if err != nil {
266+
spinner.Fail("Failed to list deployments")
267+
return util.CleanedUpSdkError{Err: err}
268+
}
269+
items := page.Items
270+
if len(items) == 0 {
271+
break
272+
}
273+
274+
g, gctx := errgroup.WithContext(cmd.Context())
275+
for _, dep := range items {
276+
g.Go(func() error {
277+
return client.Deployments.Delete(gctx, dep.ID)
278+
})
279+
}
280+
if err := g.Wait(); err != nil {
281+
spinner.Fail("Failed to delete deployments")
282+
return util.CleanedUpSdkError{Err: err}
283+
}
284+
deleted += len(items)
285+
spinner.UpdateText(fmt.Sprintf("Deleted %d deployment(s) so far...", deleted))
286+
}
287+
288+
spinner.Success(fmt.Sprintf("Deleted %d deployment(s) for app '%s'", deleted, appName))
289+
return nil
290+
}
291+
209292
func runAppHistory(cmd *cobra.Command, args []string) error {
210293
client := getKernelClient(cmd)
211294
appName := args[0]

cmd/deploy.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ import (
2323
"github.com/spf13/cobra"
2424
)
2525

26+
var deployDeleteCmd = &cobra.Command{
27+
Use: "delete <deployment_id>",
28+
Short: "Delete a deployment",
29+
Long: "Stops a running deployment and marks it for deletion. If already stopped or failed, deletes immediately.",
30+
Args: cobra.ExactArgs(1),
31+
RunE: runDeployDelete,
32+
}
33+
2634
var deployLogsCmd = &cobra.Command{
2735
Use: "logs <deployment_id>",
2836
Short: "Stream logs for a deployment",
@@ -65,6 +73,9 @@ func init() {
6573
deployLogsCmd.Flags().BoolP("with-timestamps", "t", false, "Include timestamps in each log line")
6674
deployCmd.AddCommand(deployLogsCmd)
6775

76+
deployDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
77+
deployCmd.AddCommand(deployDeleteCmd)
78+
6879
deployHistoryCmd.Flags().Int("limit", 20, "Max deployments to return (default 20)")
6980
deployHistoryCmd.Flags().Int("per-page", 20, "Items per page (alias of --limit)")
7081
deployHistoryCmd.Flags().Int("page", 1, "Page number (1-based)")
@@ -306,6 +317,33 @@ func quoteIfNeeded(s string) string {
306317
return s
307318
}
308319

320+
func runDeployDelete(cmd *cobra.Command, args []string) error {
321+
client := getKernelClient(cmd)
322+
deploymentID := args[0]
323+
skipConfirm, _ := cmd.Flags().GetBool("yes")
324+
325+
if !skipConfirm {
326+
msg := fmt.Sprintf("Are you sure you want to delete deployment '%s'? This cannot be undone.", deploymentID)
327+
pterm.DefaultInteractiveConfirm.DefaultText = msg
328+
ok, _ := pterm.DefaultInteractiveConfirm.Show()
329+
if !ok {
330+
pterm.Info.Println("Deletion cancelled")
331+
return nil
332+
}
333+
}
334+
335+
if err := client.Deployments.Delete(cmd.Context(), deploymentID); err != nil {
336+
if util.IsNotFound(err) {
337+
pterm.Warning.Printf("Deployment '%s' not found\n", deploymentID)
338+
return nil
339+
}
340+
return util.CleanedUpSdkError{Err: err}
341+
}
342+
343+
pterm.Success.Printf("Deleted deployment %s\n", deploymentID)
344+
return nil
345+
}
346+
309347
func runDeployLogs(cmd *cobra.Command, args []string) error {
310348
client := getKernelClient(cmd)
311349

cmd/profiles.go

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ import (
1212
"github.com/kernel/cli/pkg/util"
1313
"github.com/kernel/kernel-go-sdk"
1414
"github.com/kernel/kernel-go-sdk/option"
15+
"github.com/kernel/kernel-go-sdk/packages/pagination"
1516
"github.com/pterm/pterm"
17+
"github.com/samber/lo"
1618
"github.com/spf13/cobra"
1719
)
1820

1921
// ProfilesService defines the subset of the Kernel SDK profile client that we use.
2022
type ProfilesService interface {
2123
Get(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *kernel.Profile, err error)
22-
List(ctx context.Context, opts ...option.RequestOption) (res *[]kernel.Profile, err error)
24+
List(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (res *pagination.OffsetPagination[kernel.Profile], err error)
2325
Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) (err error)
2426
New(ctx context.Context, body kernel.ProfileNewParams, opts ...option.RequestOption) (res *kernel.Profile, err error)
2527
Download(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *http.Response, err error)
@@ -31,7 +33,10 @@ type ProfilesGetInput struct {
3133
}
3234

3335
type ProfilesListInput struct {
34-
Output string
36+
Output string
37+
Page int
38+
PerPage int
39+
Query string
3540
}
3641

3742
type ProfilesCreateInput struct {
@@ -60,28 +65,56 @@ func (p ProfilesCmd) List(ctx context.Context, in ProfilesListInput) error {
6065
return fmt.Errorf("unsupported --output value: use 'json'")
6166
}
6267

68+
page := in.Page
69+
perPage := in.PerPage
70+
if page <= 0 {
71+
page = 1
72+
}
73+
if perPage <= 0 {
74+
perPage = 20
75+
}
76+
6377
if in.Output != "json" {
6478
pterm.Info.Println("Fetching profiles...")
6579
}
66-
items, err := p.profiles.List(ctx)
80+
81+
params := kernel.ProfileListParams{}
82+
if in.Query != "" {
83+
params.Query = kernel.Opt(in.Query)
84+
}
85+
params.Limit = kernel.Opt(int64(perPage + 1))
86+
params.Offset = kernel.Opt(int64((page - 1) * perPage))
87+
88+
result, err := p.profiles.List(ctx, params)
6789
if err != nil {
6890
return util.CleanedUpSdkError{Err: err}
6991
}
7092

93+
var items []kernel.Profile
94+
if result != nil {
95+
items = result.Items
96+
}
97+
98+
hasMore := len(items) > perPage
99+
if hasMore {
100+
items = items[:perPage]
101+
}
102+
itemsThisPage := len(items)
103+
71104
if in.Output == "json" {
72-
if items == nil || len(*items) == 0 {
105+
if len(items) == 0 {
73106
fmt.Println("[]")
74107
return nil
75108
}
76-
return util.PrintPrettyJSONSlice(*items)
109+
return util.PrintPrettyJSONSlice(items)
77110
}
78111

79-
if items == nil || len(*items) == 0 {
112+
if len(items) == 0 {
80113
pterm.Info.Println("No profiles found")
81114
return nil
82115
}
83116
rows := pterm.TableData{{"Profile ID", "Name", "Created At", "Updated At", "Last Used At"}}
84-
for _, prof := range *items {
117+
for _, prof := range items {
85118
name := prof.Name
86119
if name == "" {
87120
name = "-"
@@ -95,6 +128,17 @@ func (p ProfilesCmd) List(ctx context.Context, in ProfilesListInput) error {
95128
})
96129
}
97130
PrintTableNoPad(rows, true)
131+
132+
pterm.Printf("\nPage: %d Per-page: %d Items this page: %d Has more: %s\n", page, perPage, itemsThisPage, lo.Ternary(hasMore, "yes", "no"))
133+
if hasMore {
134+
nextPage := page + 1
135+
nextCmd := fmt.Sprintf("kernel profile list --page %d --per-page %d", nextPage, perPage)
136+
if in.Query != "" {
137+
nextCmd += fmt.Sprintf(" --query \"%s\"", in.Query)
138+
}
139+
pterm.Printf("Next: %s\n", nextCmd)
140+
}
141+
98142
return nil
99143
}
100144

@@ -299,6 +343,9 @@ func init() {
299343
profilesCmd.AddCommand(profilesDownloadCmd)
300344

301345
profilesListCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response")
346+
profilesListCmd.Flags().Int("per-page", 20, "Items per page (default 20)")
347+
profilesListCmd.Flags().Int("page", 1, "Page number (1-based)")
348+
profilesListCmd.Flags().String("query", "", "Search profiles by name or ID")
302349
profilesGetCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response")
303350
profilesCreateCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response")
304351
profilesCreateCmd.Flags().String("name", "", "Optional unique profile name")
@@ -310,9 +357,18 @@ func init() {
310357
func runProfilesList(cmd *cobra.Command, args []string) error {
311358
client := getKernelClient(cmd)
312359
output, _ := cmd.Flags().GetString("output")
360+
perPage, _ := cmd.Flags().GetInt("per-page")
361+
page, _ := cmd.Flags().GetInt("page")
362+
query, _ := cmd.Flags().GetString("query")
363+
313364
svc := client.Profiles
314365
p := ProfilesCmd{profiles: &svc}
315-
return p.List(cmd.Context(), ProfilesListInput{Output: output})
366+
return p.List(cmd.Context(), ProfilesListInput{
367+
Output: output,
368+
Page: page,
369+
PerPage: perPage,
370+
Query: query,
371+
})
316372
}
317373

318374
func runProfilesGet(cmd *cobra.Command, args []string) error {

0 commit comments

Comments
 (0)