Skip to content
Open
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
4 changes: 2 additions & 2 deletions cmd/browsers.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ type Int64Flag struct {
Value int64
}

// Regular expression to validate CUID2 identifiers (24 lowercase alphanumeric characters).
var cuidRegex = regexp.MustCompile(`^[a-z0-9]{24}$`)
// Regular expression to validate CUID2 identifiers (starts with a letter, 24 lowercase alphanumeric characters).
var cuidRegex = regexp.MustCompile(`^[a-z][a-z0-9]{23}$`)

// getAvailableViewports returns the list of supported viewport configurations.
func getAvailableViewports() []string {
Expand Down
240 changes: 240 additions & 0 deletions cmd/projects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package cmd

import (
"encoding/json"
"fmt"

"github.com/kernel/kernel-go-sdk"
"github.com/kernel/kernel-go-sdk/packages/param"

"github.com/pterm/pterm"
"github.com/spf13/cobra"
)

var projectsCmd = &cobra.Command{
Use: "projects",
Short: "Manage projects",
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
},
}

// resolveProjectArg resolves a positional project argument that may be an ID or
// a name. If it looks like a cuid2 ID it is returned as-is; otherwise we list
// projects and find the matching name (case-insensitive).
func resolveProjectArg(cmd *cobra.Command, client kernel.Client, val string) (string, error) {
if cuidRegex.MatchString(val) {
return val, nil
}
resolved, err := resolveProjectByName(cmd.Context(), client, val)
if err != nil {
return "", err
}
return resolved, nil
}

var projectsListCmd = &cobra.Command{
Use: "list",
Short: "List projects",
RunE: func(cmd *cobra.Command, args []string) error {
client := getKernelClient(cmd)
ctx := cmd.Context()

projects, err := client.Projects.List(ctx, kernel.ProjectListParams{})
if err != nil {
pterm.Error.Println("Failed to list projects:", err)
return nil
}

if len(projects.Items) == 0 {
pterm.Info.Println("No projects found")
return nil
}

table := pterm.TableData{{"ID", "Name", "Status", "Created At"}}
for _, p := range projects.Items {
table = append(table, []string{p.ID, p.Name, string(p.Status), p.CreatedAt.String()})
}
_ = pterm.DefaultTable.WithHasHeader(true).WithData(table).Render()
return nil
},
}

var projectsCreateCmd = &cobra.Command{
Use: "create <name>",
Short: "Create a project",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client := getKernelClient(cmd)
ctx := cmd.Context()

project, err := client.Projects.New(ctx, kernel.ProjectNewParams{
CreateProjectRequest: kernel.CreateProjectRequestParam{
Name: args[0],
},
})
if err != nil {
pterm.Error.Println("Failed to create project:", err)
return nil
}

pterm.Success.Printf("Created project: %s (ID: %s)\n", project.Name, project.ID)
return nil
},
}

var projectsGetCmd = &cobra.Command{
Use: "get <id-or-name>",
Short: "Get a project by ID or name",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client := getKernelClient(cmd)
ctx := cmd.Context()

projectID, err := resolveProjectArg(cmd, client, args[0])
if err != nil {
return err
}

project, err := client.Projects.Get(ctx, projectID)
if err != nil {
pterm.Error.Println("Failed to get project:", err)
return nil
}

table := pterm.TableData{
{"Field", "Value"},
{"ID", project.ID},
{"Name", project.Name},
{"Status", string(project.Status)},
{"Created At", project.CreatedAt.String()},
{"Updated At", project.UpdatedAt.String()},
}
_ = pterm.DefaultTable.WithHasHeader(true).WithData(table).Render()
return nil
},
}

var projectsDeleteCmd = &cobra.Command{
Use: "delete <id-or-name>",
Short: "Delete a project",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client := getKernelClient(cmd)
ctx := cmd.Context()

projectID, err := resolveProjectArg(cmd, client, args[0])
if err != nil {
return err
}

err = client.Projects.Delete(ctx, projectID)
if err != nil {
pterm.Error.Println("Failed to delete project:", err)
return nil
}

pterm.Success.Printf("Deleted project: %s\n", projectID)
return nil
},
}

var projectsLimitsGetCmd = &cobra.Command{
Use: "get-limits <id-or-name>",
Short: "Get project limit overrides",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client := getKernelClient(cmd)
ctx := cmd.Context()

projectID, err := resolveProjectArg(cmd, client, args[0])
if err != nil {
return err
}

limits, err := client.Projects.Limits.Get(ctx, projectID)
if err != nil {
pterm.Error.Println("Failed to get project limits:", err)
return nil
}

out, _ := json.MarshalIndent(limits, "", " ")
fmt.Println(string(out))
return nil
},
}

var projectsLimitsSetCmd = &cobra.Command{
Use: "set-limits <id-or-name>",
Short: "Set project limit overrides",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client := getKernelClient(cmd)
ctx := cmd.Context()

projectID, err := resolveProjectArg(cmd, client, args[0])
if err != nil {
return err
}

inner := kernel.UpdateProjectLimitsRequestParam{}
limitFlags := []string{
"max-concurrent-sessions",
"max-persistent-sessions",
"max-concurrent-invocations",
"max-pooled-sessions",
}
for _, name := range limitFlags {
if cmd.Flags().Changed(name) {
v, _ := cmd.Flags().GetInt64(name)
if v < 0 {
return fmt.Errorf("--%s must be non-negative (got %d); use 0 to remove the cap", name, v)
}
}
}
if cmd.Flags().Changed("max-concurrent-sessions") {
v, _ := cmd.Flags().GetInt64("max-concurrent-sessions")
inner.MaxConcurrentSessions = param.NewOpt(v)
}
if cmd.Flags().Changed("max-persistent-sessions") {
v, _ := cmd.Flags().GetInt64("max-persistent-sessions")
inner.MaxPersistentSessions = param.NewOpt(v)
}
if cmd.Flags().Changed("max-concurrent-invocations") {
v, _ := cmd.Flags().GetInt64("max-concurrent-invocations")
inner.MaxConcurrentInvocations = param.NewOpt(v)
}
if cmd.Flags().Changed("max-pooled-sessions") {
v, _ := cmd.Flags().GetInt64("max-pooled-sessions")
inner.MaxPooledSessions = param.NewOpt(v)
}
params := kernel.ProjectLimitUpdateParams{
UpdateProjectLimitsRequest: inner,
}

limits, err := client.Projects.Limits.Update(ctx, projectID, params)
if err != nil {
pterm.Error.Println("Failed to set project limits:", err)
return nil
}

out, _ := json.MarshalIndent(limits, "", " ")
pterm.Success.Println("Project limits updated:")
fmt.Println(string(out))
return nil
},
}

func init() {
projectsLimitsSetCmd.Flags().Int64("max-concurrent-sessions", 0, "Maximum concurrent browser sessions (0 to remove cap)")
projectsLimitsSetCmd.Flags().Int64("max-persistent-sessions", 0, "Maximum persistent browser sessions (0 to remove cap)")
projectsLimitsSetCmd.Flags().Int64("max-concurrent-invocations", 0, "Maximum concurrent app invocations (0 to remove cap)")
projectsLimitsSetCmd.Flags().Int64("max-pooled-sessions", 0, "Maximum pooled sessions capacity (0 to remove cap)")

projectsCmd.AddCommand(projectsListCmd)
projectsCmd.AddCommand(projectsCreateCmd)
projectsCmd.AddCommand(projectsGetCmd)
projectsCmd.AddCommand(projectsDeleteCmd)
projectsCmd.AddCommand(projectsLimitsGetCmd)
projectsCmd.AddCommand(projectsLimitsSetCmd)
}
2 changes: 1 addition & 1 deletion cmd/proxies/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (p ProxyCmd) Check(ctx context.Context, in ProxyCheckInput) error {
pterm.Info.Printf("Running health check on proxy %s...\n", in.ID)
}

proxy, err := p.proxies.Check(ctx, in.ID)
proxy, err := p.proxies.Check(ctx, in.ID, kernel.ProxyCheckParams{})
if err != nil {
return util.CleanedUpSdkError{Err: err}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/proxies/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestProxyCheck_ShowsBypassHosts(t *testing.T) {
buf := captureOutput(t)

fake := &FakeProxyService{
CheckFunc: func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.ProxyCheckResponse, error) {
CheckFunc: func(ctx context.Context, id string, body kernel.ProxyCheckParams, opts ...option.RequestOption) (*kernel.ProxyCheckResponse, error) {
return &kernel.ProxyCheckResponse{
ID: id,
Name: "Proxy 1",
Expand Down
6 changes: 3 additions & 3 deletions cmd/proxies/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type FakeProxyService struct {
GetFunc func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.ProxyGetResponse, error)
NewFunc func(ctx context.Context, body kernel.ProxyNewParams, opts ...option.RequestOption) (*kernel.ProxyNewResponse, error)
DeleteFunc func(ctx context.Context, id string, opts ...option.RequestOption) error
CheckFunc func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.ProxyCheckResponse, error)
CheckFunc func(ctx context.Context, id string, body kernel.ProxyCheckParams, opts ...option.RequestOption) (*kernel.ProxyCheckResponse, error)
}

func (f *FakeProxyService) List(ctx context.Context, opts ...option.RequestOption) (*[]kernel.ProxyListResponse, error) {
Expand Down Expand Up @@ -73,9 +73,9 @@ func (f *FakeProxyService) Delete(ctx context.Context, id string, opts ...option
return nil
}

func (f *FakeProxyService) Check(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.ProxyCheckResponse, error) {
func (f *FakeProxyService) Check(ctx context.Context, id string, body kernel.ProxyCheckParams, opts ...option.RequestOption) (*kernel.ProxyCheckResponse, error) {
if f.CheckFunc != nil {
return f.CheckFunc(ctx, id, opts...)
return f.CheckFunc(ctx, id, body, opts...)
}
return &kernel.ProxyCheckResponse{ID: id, Type: kernel.ProxyCheckResponseTypeDatacenter}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/proxies/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type ProxyService interface {
Get(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.ProxyGetResponse, err error)
New(ctx context.Context, body kernel.ProxyNewParams, opts ...option.RequestOption) (res *kernel.ProxyNewResponse, err error)
Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error)
Check(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.ProxyCheckResponse, err error)
Check(ctx context.Context, id string, body kernel.ProxyCheckParams, opts ...option.RequestOption) (res *kernel.ProxyCheckResponse, err error)
}

// ProxyCmd handles proxy operations independent of cobra.
Expand Down
Loading
Loading