Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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{
Comment thread
masnwilliams marked this conversation as resolved.
Outdated
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>",
Comment thread
masnwilliams marked this conversation as resolved.
Outdated
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, "", " ")
Comment thread
masnwilliams marked this conversation as resolved.
Outdated
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)
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}
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) {
Comment thread
cursor[bot] marked this conversation as resolved.
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