Skip to content

Commit 794155c

Browse files
feat: added env promote, create, sleep command
1 parent b46f4a1 commit 794155c

13 files changed

Lines changed: 401 additions & 26 deletions

File tree

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,20 +208,23 @@ Sensitive and noisy files are always excluded: `.env`, `.env.*`, secrets/keys (`
208208

209209
### Deployments
210210

211-
| Command | Description |
212-
| --------------------------------- | ------------------------------------ |
213-
| `createos deployments list` | List deployments for a project |
214-
| `createos deployments logs` | Stream runtime logs for a deployment |
215-
| `createos deployments build-logs` | Stream build logs for a deployment |
216-
| `createos deployments retrigger` | Retrigger a deployment |
217-
| `createos deployments wakeup` | Wake up a sleeping deployment |
218-
| `createos deployments cancel` | Cancel a running deployment |
211+
| Command | Description |
212+
| ------------------------------------- | -------------------------------------- |
213+
| `createos deployments list` | List deployments for a project |
214+
| `createos deployments logs` | Stream runtime logs for a deployment |
215+
| `createos deployments build-logs` | Stream build logs for a deployment |
216+
| `createos deployments promote` | Promote a deployment to an environment |
217+
| `createos deployments retrigger` | Retrigger a deployment |
218+
| `createos deployments sleep` | Put a running deployment to sleep |
219+
| `createos deployments wakeup` | Wake up a sleeping deployment |
220+
| `createos deployments cancel-build` | Cancel a deployment that is building |
219221

220222
### Environments
221223

222224
| Command | Description |
223225
| ------------------------------ | ------------------------------- |
224226
| `createos environments list` | List environments for a project |
227+
| `createos environments create` | Create a new environment |
225228
| `createos environments delete` | Delete an environment |
226229

227230
### Environment Variables
@@ -357,12 +360,15 @@ createos projects unsuspend --project <id> --force
357360
createos deployments list --project <id>
358361
createos deployments logs --project <id> --deployment <id>
359362
createos deployments build-logs --project <id> --deployment <id>
363+
createos deployments promote --project <id> --deployment <id> --environment <id>
360364
createos deployments retrigger --project <id> --deployment <id>
365+
createos deployments sleep --project <id> --deployment <id> --force
361366
createos deployments wakeup --project <id> --deployment <id>
362-
createos deployments cancel --project <id> --deployment <id> --force
367+
createos deployments cancel-build --project <id> --deployment <id> --force
363368

364369
# Environments
365370
createos environments list --project <id>
371+
createos environments create --project <id> --name "Staging" --unique-name staging --branch develop
366372
createos environments delete --project <id> --environment <id> --force
367373

368374
# Environment variables

cmd/deployments/build_logs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func newDeploymentBuildLogsCommand() *cli.Command {
3737
return fmt.Errorf("you're not signed in — run 'createos login' to get started")
3838
}
3939

40-
projectID, deploymentID, err := resolveDeployment(c, client)
40+
projectID, deploymentID, err := resolveDeployment(c, client, nil)
4141
if err != nil {
4242
return err
4343
}

cmd/deployments/cancel.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212

1313
func newDeploymentDeleteCommand() *cli.Command {
1414
return &cli.Command{
15-
Name: "cancel",
16-
Usage: "Cancel a running deployment",
15+
Name: "cancel-build",
16+
Usage: "Cancel a deployment that is building",
1717
Flags: []cli.Flag{
1818
&cli.StringFlag{Name: "project", Usage: "Project ID"},
1919
&cli.StringFlag{Name: "deployment", Usage: "Deployment ID"},
@@ -25,7 +25,7 @@ func newDeploymentDeleteCommand() *cli.Command {
2525
return fmt.Errorf("you're not signed in — run 'createos login' to get started")
2626
}
2727

28-
projectID, deploymentID, err := resolveDeployment(c, client)
28+
projectID, deploymentID, err := resolveDeployment(c, client, []string{"building", "queue"})
2929
if err != nil {
3030
return err
3131
}

cmd/deployments/deployments.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ func NewDeploymentsCommand() *cli.Command {
1414
newDeploymentsListCommand(),
1515
newDeploymentBuildLogsCommand(),
1616
newDeploymentLogsCommand(),
17+
newDeploymentPromoteCommand(),
1718
newDeploymentRetriggerCommand(),
19+
newDeploymentSleepCommand(),
1820
newDeploymentDeleteCommand(),
1921
newDeploymentWakeupCommand(),
2022
},

cmd/deployments/helpers.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import (
1313

1414
// resolveDeployment resolves projectID and deploymentID from flags or interactively.
1515
// Uses --project and --deployment flags; falls back to config and interactive select.
16-
func resolveDeployment(c *cli.Context, client *api.APIClient) (string, string, error) {
16+
// Optional statusFilter limits which deployments are shown; pass nil to show all.
17+
func resolveDeployment(c *cli.Context, client *api.APIClient, statusFilter []string) (string, string, error) {
1718
projectID, err := cmdutil.ResolveProjectID(c.String("project"))
1819
if err != nil {
1920
return "", "", err
@@ -23,25 +24,67 @@ func resolveDeployment(c *cli.Context, client *api.APIClient) (string, string, e
2324
return projectID, deploymentID, nil
2425
}
2526

26-
deploymentID, err := pickDeployment(client, projectID)
27+
deploymentID, err := pickDeployment(client, projectID, statusFilter)
2728
if err != nil {
2829
return "", "", err
2930
}
3031
return projectID, deploymentID, nil
3132
}
3233

33-
func pickDeployment(client *api.APIClient, projectID string) (string, error) {
34-
deployments, err := client.ListDeployments(projectID)
34+
func pickDeployment(client *api.APIClient, projectID string, statusFilter []string) (string, error) {
35+
allDeployments, err := client.ListDeployments(projectID)
3536
if err != nil {
3637
return "", err
3738
}
38-
if len(deployments) == 0 {
39+
if len(allDeployments) == 0 {
3940
return "", fmt.Errorf("no deployments found for this project")
4041
}
41-
if len(deployments) == 1 {
42-
return deployments[0].ID, nil
42+
43+
// Filter by status if a filter is provided
44+
deployments := allDeployments
45+
if len(statusFilter) > 0 {
46+
allowed := make(map[string]bool, len(statusFilter))
47+
for _, s := range statusFilter {
48+
allowed[s] = true
49+
}
50+
filtered := make([]api.Deployment, 0, len(allDeployments))
51+
for _, d := range allDeployments {
52+
if allowed[d.Status] {
53+
filtered = append(filtered, d)
54+
}
55+
}
56+
deployments = filtered
4357
}
4458

59+
if len(deployments) == 0 {
60+
return "", fmt.Errorf("no eligible deployments found for this action")
61+
}
62+
63+
// Non-interactive: auto-pick single, error on multiple
64+
if !terminal.IsInteractive() {
65+
if len(deployments) == 1 {
66+
return deployments[0].ID, nil
67+
}
68+
return "", fmt.Errorf("multiple deployments found — use --deployment <id> to specify one\n\n To see your deployments, run:\n createos deployments list")
69+
}
70+
71+
// Interactive: show table then let user pick
72+
tableData := pterm.TableData{
73+
{"#", "ID", "Status", "URL", "Created At"},
74+
}
75+
for i, d := range deployments {
76+
tableData = append(tableData, []string{
77+
fmt.Sprintf("%d", i+1),
78+
d.ID,
79+
d.Status,
80+
d.Extra.Endpoint,
81+
d.CreatedAt.Format("2006-01-02 15:04:05"),
82+
})
83+
}
84+
fmt.Println()
85+
_ = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
86+
fmt.Println()
87+
4588
options := make([]string, len(deployments))
4689
for i, d := range deployments {
4790
id := d.ID
@@ -62,9 +105,7 @@ func pickDeployment(client *api.APIClient, projectID string) (string, error) {
62105
}
63106
options[i] = label
64107
}
65-
if !terminal.IsInteractive() {
66-
return "", fmt.Errorf("multiple deployments found — use --deployment <id> to specify one\n\n To see your deployments, run:\n createos deployments list")
67-
}
108+
68109
selected, err := pterm.DefaultInteractiveSelect.
69110
WithOptions(options).
70111
WithDefaultText("Select a deployment").

cmd/deployments/logs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func newDeploymentLogsCommand() *cli.Command {
3838
return fmt.Errorf("you're not signed in — run 'createos login' to get started")
3939
}
4040

41-
projectID, deploymentID, err := resolveDeployment(c, client)
41+
projectID, deploymentID, err := resolveDeployment(c, client, nil)
4242
if err != nil {
4343
return err
4444
}

cmd/deployments/promote.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package deployments
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/pterm/pterm"
7+
"github.com/urfave/cli/v2"
8+
9+
"github.com/NodeOps-app/createos-cli/internal/api"
10+
"github.com/NodeOps-app/createos-cli/internal/terminal"
11+
)
12+
13+
// promotableStatuses are deployment statuses whose artifact has been pushed.
14+
var promotableStatuses = []string{"deployed", "deploying", "crashing", "sleeping", "terminating"}
15+
16+
func newDeploymentPromoteCommand() *cli.Command {
17+
return &cli.Command{
18+
Name: "promote",
19+
Usage: "Promote a deployment to an environment",
20+
Flags: []cli.Flag{
21+
&cli.StringFlag{Name: "project", Usage: "Project ID"},
22+
&cli.StringFlag{Name: "deployment", Usage: "Deployment ID"},
23+
&cli.StringFlag{Name: "environment", Usage: "Environment ID"},
24+
},
25+
Action: func(c *cli.Context) error {
26+
client, ok := c.App.Metadata[api.ClientKey].(*api.APIClient)
27+
if !ok {
28+
return fmt.Errorf("you're not signed in — run 'createos login' to get started")
29+
}
30+
31+
projectID, deploymentID, err := resolveDeployment(c, client, promotableStatuses)
32+
if err != nil {
33+
return err
34+
}
35+
36+
environmentID := c.String("environment")
37+
if environmentID == "" {
38+
environmentID, err = pickEnvironment(client, projectID)
39+
if err != nil {
40+
return err
41+
}
42+
}
43+
44+
if err := client.PromoteDeployment(projectID, environmentID, deploymentID); err != nil {
45+
return err
46+
}
47+
48+
pterm.Success.Println("Deployment promoted to environment.")
49+
return nil
50+
},
51+
}
52+
}
53+
54+
func pickEnvironment(client *api.APIClient, projectID string) (string, error) {
55+
envs, err := client.ListEnvironments(projectID)
56+
if err != nil {
57+
return "", err
58+
}
59+
if len(envs) == 0 {
60+
return "", fmt.Errorf("no environments found for this project\n\n Create one first with:\n createos environments create")
61+
}
62+
if len(envs) == 1 {
63+
return envs[0].ID, nil
64+
}
65+
66+
options := make([]string, len(envs))
67+
for i, e := range envs {
68+
options[i] = fmt.Sprintf("%s (%s)", e.DisplayName, e.Status)
69+
}
70+
if !terminal.IsInteractive() {
71+
return "", fmt.Errorf("multiple environments found — use --environment <id> to specify one\n\n To see your environments, run:\n createos environments list")
72+
}
73+
selected, err := pterm.DefaultInteractiveSelect.
74+
WithOptions(options).
75+
WithDefaultText("Select an environment to promote to").
76+
Show()
77+
if err != nil {
78+
return "", fmt.Errorf("could not read selection: %w", err)
79+
}
80+
for i, opt := range options {
81+
if opt == selected {
82+
return envs[i].ID, nil
83+
}
84+
}
85+
return "", fmt.Errorf("no environment selected")
86+
}

cmd/deployments/retrigger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func newDeploymentRetriggerCommand() *cli.Command {
2323
return fmt.Errorf("you're not signed in — run 'createos login' to get started")
2424
}
2525

26-
projectID, deploymentID, err := resolveDeployment(c, client)
26+
projectID, deploymentID, err := resolveDeployment(c, client, nil)
2727
if err != nil {
2828
return err
2929
}

cmd/deployments/sleep.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package deployments
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/pterm/pterm"
7+
"github.com/urfave/cli/v2"
8+
9+
"github.com/NodeOps-app/createos-cli/internal/api"
10+
"github.com/NodeOps-app/createos-cli/internal/terminal"
11+
)
12+
13+
func newDeploymentSleepCommand() *cli.Command {
14+
return &cli.Command{
15+
Name: "sleep",
16+
Usage: "Put a running deployment to sleep",
17+
Flags: []cli.Flag{
18+
&cli.StringFlag{Name: "project", Usage: "Project ID"},
19+
&cli.StringFlag{Name: "deployment", Usage: "Deployment ID"},
20+
&cli.BoolFlag{Name: "force", Aliases: []string{"f"}, Usage: "Skip confirmation prompt"},
21+
},
22+
Action: func(c *cli.Context) error {
23+
client, ok := c.App.Metadata[api.ClientKey].(*api.APIClient)
24+
if !ok {
25+
return fmt.Errorf("you're not signed in — run 'createos login' to get started")
26+
}
27+
28+
projectID, deploymentID, err := resolveDeployment(c, client, []string{"deployed", "deploying", "crashing"})
29+
if err != nil {
30+
return err
31+
}
32+
33+
if !c.Bool("force") {
34+
if !terminal.IsInteractive() {
35+
return fmt.Errorf("confirmation required — use --force to sleep without a prompt")
36+
}
37+
38+
confirm, err := pterm.DefaultInteractiveConfirm.
39+
WithDefaultText(fmt.Sprintf("Are you sure you want to put deployment %q to sleep?", deploymentID)).
40+
WithDefaultValue(false).
41+
Show()
42+
if err != nil {
43+
return fmt.Errorf("could not read confirmation: %w", err)
44+
}
45+
46+
if !confirm {
47+
fmt.Println("Cancelled. Your deployment was not put to sleep.")
48+
return nil
49+
}
50+
}
51+
52+
if err := client.SleepDeployment(projectID, deploymentID); err != nil {
53+
return err
54+
}
55+
56+
pterm.Success.Println("Deployment is being put to sleep.")
57+
return nil
58+
},
59+
}
60+
}

cmd/deployments/wakeup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func newDeploymentWakeupCommand() *cli.Command {
2323
return fmt.Errorf("you're not signed in — run 'createos login' to get started")
2424
}
2525

26-
projectID, deploymentID, err := resolveDeployment(c, client)
26+
projectID, deploymentID, err := resolveDeployment(c, client, []string{"sleeping"})
2727
if err != nil {
2828
return err
2929
}

0 commit comments

Comments
 (0)