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
77 changes: 77 additions & 0 deletions cmd/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type ProjectsService interface {
ProjectListService
New(ctx context.Context, body kernel.ProjectNewParams, opts ...option.RequestOption) (res *kernel.Project, err error)
Get(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.Project, err error)
Update(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (res *kernel.Project, err error)
Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error)
}

Expand All @@ -45,6 +46,14 @@ type ProjectsGetInput struct {
Identifier string
}

type ProjectsUpdateInput struct {
Identifier string
Name string
NameSet bool
Status string
StatusSet bool
}

type ProjectsDeleteInput struct {
Identifier string
}
Expand Down Expand Up @@ -133,6 +142,50 @@ func (c ProjectsCmd) Get(ctx context.Context, in ProjectsGetInput) error {
return nil
}

func (c ProjectsCmd) Update(ctx context.Context, in ProjectsUpdateInput) error {
if !in.NameSet && !in.StatusSet {
return fmt.Errorf("must provide at least one of --name or --status")
}

projectID, err := resolveProjectArg(ctx, c.projects, in.Identifier)
if err != nil {
return err
}

inner := kernel.UpdateProjectRequestParam{}
if in.NameSet {
inner.Name = param.NewOpt(in.Name)
}
if in.StatusSet {
switch in.Status {
case "active":
inner.Status = kernel.UpdateProjectRequestStatusActive
case "archived":
inner.Status = kernel.UpdateProjectRequestStatusArchived
default:
return fmt.Errorf("--status must be one of: active, archived (got %q)", in.Status)
}
}

project, err := c.projects.Update(ctx, projectID, kernel.ProjectUpdateParams{
UpdateProjectRequest: inner,
})
if err != nil {
return util.CleanedUpSdkError{Err: err}
}

pterm.Success.Printf("Updated project: %s (ID: %s)\n", project.Name, project.ID)
table := pterm.TableData{
{"Field", "Value"},
{"ID", project.ID},
{"Name", project.Name},
{"Status", string(project.Status)},
{"Updated At", util.FormatLocal(project.UpdatedAt)},
}
PrintTableNoPad(table, true)
return nil
}

func (c ProjectsCmd) Delete(ctx context.Context, in ProjectsDeleteInput) error {
projectID, err := resolveProjectArg(ctx, c.projects, in.Identifier)
if err != nil {
Expand Down Expand Up @@ -280,6 +333,19 @@ func runProjectsGet(cmd *cobra.Command, args []string) error {
return c.Get(cmd.Context(), ProjectsGetInput{Identifier: args[0]})
}

func runProjectsUpdate(cmd *cobra.Command, args []string) error {
c := getProjectsHandler(cmd)
name, _ := cmd.Flags().GetString("name")
status, _ := cmd.Flags().GetString("status")
return c.Update(cmd.Context(), ProjectsUpdateInput{
Identifier: args[0],
Name: name,
NameSet: cmd.Flags().Changed("name"),
Status: status,
StatusSet: cmd.Flags().Changed("status"),
})
}

func runProjectsDelete(cmd *cobra.Command, args []string) error {
c := getProjectsHandler(cmd)
return c.Delete(cmd.Context(), ProjectsDeleteInput{Identifier: args[0]})
Expand Down Expand Up @@ -364,6 +430,13 @@ var projectsGetCmd = &cobra.Command{
RunE: runProjectsGet,
}

var projectsUpdateCmd = &cobra.Command{
Use: "update <id-or-name>",
Short: "Update a project (rename or change status)",
Args: cobra.ExactArgs(1),
RunE: runProjectsUpdate,
}

var projectsDeleteCmd = &cobra.Command{
Use: "delete <id-or-name>",
Short: "Delete a project",
Expand Down Expand Up @@ -418,9 +491,13 @@ func init() {
projectsLimitsCmd.AddCommand(projectsLimitsGetCmd)
projectsLimitsCmd.AddCommand(projectsLimitsSetCmd)

projectsUpdateCmd.Flags().String("name", "", "New project name")
projectsUpdateCmd.Flags().String("status", "", "New project status (active, archived)")

projectsCmd.AddCommand(projectsListCmd)
projectsCmd.AddCommand(projectsCreateCmd)
projectsCmd.AddCommand(projectsGetCmd)
projectsCmd.AddCommand(projectsUpdateCmd)
projectsCmd.AddCommand(projectsDeleteCmd)
projectsCmd.AddCommand(projectsLimitsCmd)
projectsCmd.AddCommand(projectsGetLimitsCompatCmd)
Expand Down
51 changes: 51 additions & 0 deletions cmd/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type FakeProjectsService struct {
ListFunc func(ctx context.Context, query kernel.ProjectListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Project], error)
NewFunc func(ctx context.Context, body kernel.ProjectNewParams, opts ...option.RequestOption) (*kernel.Project, error)
GetFunc func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.Project, error)
UpdateFunc func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error)
DeleteFunc func(ctx context.Context, id string, opts ...option.RequestOption) error
}

Expand All @@ -66,6 +67,13 @@ func (f *FakeProjectsService) Get(ctx context.Context, id string, opts ...option
return &kernel.Project{ID: id, Name: "default"}, nil
}

func (f *FakeProjectsService) Update(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) {
if f.UpdateFunc != nil {
return f.UpdateFunc(ctx, id, body, opts...)
}
return &kernel.Project{ID: id, Name: body.UpdateProjectRequest.Name.Value, Status: kernel.ProjectStatusActive}, nil
}

func (f *FakeProjectsService) Delete(ctx context.Context, id string, opts ...option.RequestOption) error {
if f.DeleteFunc != nil {
return f.DeleteFunc(ctx, id, opts...)
Expand All @@ -92,6 +100,49 @@ func (f *FakeProjectLimitsService) Update(ctx context.Context, id string, body k
return &kernel.ProjectLimits{}, nil
}

func TestProjectsUpdate_RenamesAndUpdatesStatus(t *testing.T) {
captureProjectsOutput(t)

var captured kernel.ProjectUpdateParams
fakeProjects := &FakeProjectsService{
UpdateFunc: func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) {
captured = body
return &kernel.Project{ID: id, Name: "renamed", Status: kernel.ProjectStatusArchived}, nil
},
}
c := ProjectsCmd{projects: fakeProjects, limits: &FakeProjectLimitsService{}}

err := c.Update(context.Background(), ProjectsUpdateInput{
Identifier: "a12345678901234567890123",
Name: "renamed",
NameSet: true,
Status: "archived",
StatusSet: true,
})
assert.NoError(t, err)
assert.True(t, captured.UpdateProjectRequest.Name.Valid())
assert.Equal(t, "renamed", captured.UpdateProjectRequest.Name.Value)
assert.Equal(t, kernel.UpdateProjectRequestStatusArchived, captured.UpdateProjectRequest.Status)
}

func TestProjectsUpdate_RequiresAtLeastOneFlag(t *testing.T) {
c := ProjectsCmd{projects: &FakeProjectsService{}, limits: &FakeProjectLimitsService{}}
err := c.Update(context.Background(), ProjectsUpdateInput{Identifier: "a12345678901234567890123"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "at least one of --name or --status")
}

func TestProjectsUpdate_RejectsUnknownStatus(t *testing.T) {
c := ProjectsCmd{projects: &FakeProjectsService{}, limits: &FakeProjectLimitsService{}}
err := c.Update(context.Background(), ProjectsUpdateInput{
Identifier: "a12345678901234567890123",
Status: "deleted",
StatusSet: true,
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "active, archived")
}

func TestProjectsLimitsGet_DefaultOutput(t *testing.T) {
buf := captureProjectsOutput(t)
limits := &kernel.ProjectLimits{
Expand Down
Loading