Skip to content
Merged
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
6 changes: 5 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## v5.7.0 (unreleased)
## v5.8.0 (unreleased)

## v5.7.0 (2025-07-18)
* Add `--wait` and `--wait-timeout` to `app init`, `service create`, `service update`, `service redeploy` and `deploy`.
* By default the `--wait-timeout` duration will be 5 minutes, but can be changed by using like this: `--wait-timeout 1m`

## v5.6.0 (2025-07-17)
* Add koyeb compose which is docker compose like functionality.
Expand Down
7 changes: 4 additions & 3 deletions pkg/koyeb/apps.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package koyeb

import (
"time"

"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -46,12 +48,11 @@ func NewAppCmd() *cobra.Command {

createService.SetDefinition(*createDefinition)

wait, _ := cmd.Flags().GetBool("wait")

return h.Init(ctx, cmd, args, createApp, createService, wait)
return h.Init(ctx, cmd, args, createApp, createService)
}),
}
initAppCmd.Flags().Bool("wait", false, "Waits until app deployment is done")
initAppCmd.Flags().Duration("wait-timeout", 5*time.Minute, "Duration the wait will last until timeout")
appCmd.AddCommand(initAppCmd)
serviceHandler.addServiceDefinitionFlags(initAppCmd.Flags())

Expand Down
27 changes: 21 additions & 6 deletions pkg/koyeb/apps_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import (
"github.com/spf13/cobra"
)

func (h *AppHandler) Init(ctx *CLIContext, cmd *cobra.Command, args []string, createApp *koyeb.CreateApp, createService *koyeb.CreateService, wait bool) error {
func (h *AppHandler) Init(ctx *CLIContext, cmd *cobra.Command, args []string, createApp *koyeb.CreateApp, createService *koyeb.CreateService) error {
wait, _ := cmd.Flags().GetBool("wait")
waitTimeout, err := cmd.Flags().GetDuration("wait-timeout")
if err != nil {
return err
}

uid := uuid.Must(uuid.NewV4())
createService.SetAppId(uid.String())
_, resp, err := ctx.Client.ServicesApi.CreateService(ctx.Context).DryRun(true).Service(*createService).Execute()
Expand Down Expand Up @@ -66,7 +72,7 @@ func (h *AppHandler) Init(ctx *CLIContext, cmd *cobra.Command, args []string, cr
serviceRes.Service.GetId()[:8],
)

ctxd, cancel := context.WithTimeout(ctx.Context, 5*time.Minute)
ctxd, cancel := context.WithTimeout(ctx.Context, waitTimeout)
defer cancel()

for range ticker(ctxd, 2*time.Second) {
Expand All @@ -79,17 +85,26 @@ func (h *AppHandler) Init(ctx *CLIContext, cmd *cobra.Command, args []string, cr
)
}

if getServiceRes.Service != nil && getServiceRes.Service.Status != nil &&
*getServiceRes.Service.Status != koyeb.SERVICESTATUS_STARTING {
return nil
if getServiceRes.Service != nil && getServiceRes.Service.Status != nil {
switch status := *getServiceRes.Service.Status; status {
case koyeb.SERVICESTATUS_DELETED, koyeb.SERVICESTATUS_DEGRADED, koyeb.SERVICESTATUS_UNHEALTHY:
return fmt.Errorf("Service %s deployment ended in status: %s", serviceRes.Service.GetId()[:8], status)
case koyeb.SERVICESTATUS_STARTING, koyeb.SERVICESTATUS_RESUMING, koyeb.SERVICESTATUS_DELETING, koyeb.SERVICESTATUS_PAUSING:
break
default:
return nil
}
}
}

log.Infof("Service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb service logs %s -t build`. For the runtime logs, run `koyeb service logs %s`",
serviceRes.Service.GetId()[:8],
serviceRes.Service.GetId()[:8],
)
return nil
return fmt.Errorf("service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb service logs %s -t build`. For the runtime logs, run `koyeb service logs %s`",
serviceRes.Service.GetId()[:8],
serviceRes.Service.GetId()[:8],
)
}

return nil
Expand Down
9 changes: 5 additions & 4 deletions pkg/koyeb/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package koyeb
import (
"fmt"
"strconv"
"time"

"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
"github.com/koyeb/koyeb-cli/pkg/koyeb/errors"
Expand All @@ -15,7 +16,6 @@ func NewDeployCmd() *cobra.Command {
appHandler := NewAppHandler()
archiveHandler := NewArchiveHandler()
serviceHandler := NewServiceHandler()
wait := false

deployCmd := &cobra.Command{
Use: "deploy <path> <app>/<service>",
Expand Down Expand Up @@ -86,7 +86,7 @@ func NewDeployCmd() *cobra.Command {
createService.SetDefinition(*createDefinition)

log.Infof("Creating the new service `%s`", serviceName)
if err := serviceHandler.Create(ctx, cmd, []string{args[1]}, createService, wait); err != nil {
if err := serviceHandler.Create(ctx, cmd, []string{args[1]}, createService); err != nil {
return err
}
} else {
Expand Down Expand Up @@ -143,15 +143,16 @@ func NewDeployCmd() *cobra.Command {
updateService.SetDefinition(*updateDefinition)

log.Infof("Updating the existing service `%s`", serviceName)
if err := serviceHandler.Update(ctx, cmd, []string{args[1]}, updateService, wait); err != nil {
if err := serviceHandler.Update(ctx, cmd, []string{args[1]}, updateService); err != nil {
return err
}
}
return nil
}),
}
deployCmd.Flags().String("app", "", "Service application. Can also be provided in the service name with the format <app>/<service>")
deployCmd.Flags().BoolVar(&wait, "wait", false, "Waits until the deployment is done")
deployCmd.Flags().Bool("wait", false, "Waits until the deployment is done")
deployCmd.Flags().Duration("wait-timeout", 5*time.Minute, "Duration the wait will last until timeout")

serviceHandler.addServiceDefinitionFlagsForAllSources(deployCmd.Flags())
serviceHandler.addServiceDefinitionFlagsForArchiveSource(deployCmd.Flags())
Expand Down
11 changes: 10 additions & 1 deletion pkg/koyeb/flags.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
package koyeb

import "github.com/spf13/cobra"
import (
"time"

"github.com/spf13/cobra"
)

func GetDurationFlags(cmd *cobra.Command, name string) time.Duration {
val, _ := cmd.Flags().GetDuration(name)
return val
}

func GetBoolFlags(cmd *cobra.Command, name string) bool {
val, _ := cmd.Flags().GetBool(name)
Expand Down
12 changes: 6 additions & 6 deletions pkg/koyeb/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"regexp"
"strconv"
"strings"
"time"

"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
"github.com/koyeb/koyeb-cli/pkg/koyeb/dates"
Expand Down Expand Up @@ -61,14 +62,13 @@ $> koyeb service create myservice --app myapp --docker nginx --port 80:tcp
createDefinition.Name = koyeb.PtrString(serviceName)
createService.SetDefinition(*createDefinition)

wait, _ := cmd.Flags().GetBool("wait")

return h.Create(ctx, cmd, args, createService, wait)
return h.Create(ctx, cmd, args, createService)
}),
}
h.addServiceDefinitionFlags(createServiceCmd.Flags())
createServiceCmd.Flags().StringP("app", "a", "", "Service application")
createServiceCmd.Flags().Bool("wait", false, "Waits until service deployment is done")
createServiceCmd.Flags().Duration("wait-timeout", 5*time.Minute, "Duration the wait will last until timeout")
serviceCmd.AddCommand(createServiceCmd)

getServiceCmd := &cobra.Command{
Expand Down Expand Up @@ -224,9 +224,7 @@ $> koyeb service update myapp/myservice --port 80:tcp --route '!/'
saveOnly, _ := cmd.Flags().GetBool("save-only")
updateService.SetSaveOnly(saveOnly)

wait, _ := cmd.Flags().GetBool("wait")

return h.Update(ctx, cmd, args, updateService, wait)
return h.Update(ctx, cmd, args, updateService)
}),
}
h.addServiceDefinitionFlags(updateServiceCmd.Flags())
Expand All @@ -236,6 +234,7 @@ $> koyeb service update myapp/myservice --port 80:tcp --route '!/'
updateServiceCmd.Flags().Bool("skip-build", false, "If there has been at least one past successfully build deployment, use the last one instead of rebuilding. WARNING: this can lead to unexpected behavior if the build depends, for example, on environment variables.")
updateServiceCmd.Flags().Bool("save-only", false, "Save the new configuration without deploying it")
updateServiceCmd.Flags().Bool("wait", false, "Waits until the service deployment is done")
updateServiceCmd.Flags().Duration("wait-timeout", 5*time.Minute, "Duration the wait will last until timeout")
serviceCmd.AddCommand(updateServiceCmd)

redeployServiceCmd := &cobra.Command{
Expand All @@ -247,6 +246,7 @@ $> koyeb service update myapp/myservice --port 80:tcp --route '!/'
redeployServiceCmd.Flags().StringP("app", "a", "", "Service application")
redeployServiceCmd.Flags().Bool("skip-build", false, "If there has been at least one past successfully build deployment, use the last one instead of rebuilding. WARNING: this can lead to unexpected behavior if the build depends, for example, on environment variables.")
redeployServiceCmd.Flags().Bool("wait", false, "Waits until service deployment is done.")
redeployServiceCmd.Flags().Duration("wait-timeout", 5*time.Minute, "Duration the wait will last until timeout")
serviceCmd.AddCommand(redeployServiceCmd)
redeployServiceCmd.Flags().Bool("use-cache", false, "Use cache to redeploy")

Expand Down
24 changes: 18 additions & 6 deletions pkg/koyeb/services_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/spf13/cobra"
)

func (h *ServiceHandler) Create(ctx *CLIContext, cmd *cobra.Command, args []string, createService *koyeb.CreateService, wait bool) error {
func (h *ServiceHandler) Create(ctx *CLIContext, cmd *cobra.Command, args []string, createService *koyeb.CreateService) error {
appID, err := h.parseAppName(cmd, args[0])
if err != nil {
return err
Expand All @@ -22,6 +22,9 @@ func (h *ServiceHandler) Create(ctx *CLIContext, cmd *cobra.Command, args []stri
return err
}

wait, _ := cmd.Flags().GetBool("wait")
waitTimeout, _ := cmd.Flags().GetDuration("wait-timeout")

resApp, resp, err := ctx.Client.AppsApi.GetApp(ctx.Context, app).Execute()
if err != nil {
return errors.NewCLIErrorFromAPIError(
Expand Down Expand Up @@ -56,7 +59,7 @@ func (h *ServiceHandler) Create(ctx *CLIContext, cmd *cobra.Command, args []stri
}()

if wait {
ctxd, cancel := context.WithTimeout(ctx.Context, 5*time.Minute)
ctxd, cancel := context.WithTimeout(ctx.Context, waitTimeout)
defer cancel()

for range ticker(ctxd, 2*time.Second) {
Expand All @@ -69,17 +72,26 @@ func (h *ServiceHandler) Create(ctx *CLIContext, cmd *cobra.Command, args []stri
)
}

if res.Service != nil && res.Service.Status != nil &&
*res.Service.Status != koyeb.SERVICESTATUS_STARTING {
return nil
if res.Service != nil && res.Service.Status != nil {
switch status := *res.Service.Status; status {
case koyeb.SERVICESTATUS_DELETED, koyeb.SERVICESTATUS_DEGRADED, koyeb.SERVICESTATUS_UNHEALTHY:
return fmt.Errorf("Service %s deployment ended in status: %s", res.Service.GetId()[:8], status)
case koyeb.SERVICESTATUS_STARTING, koyeb.SERVICESTATUS_RESUMING, koyeb.SERVICESTATUS_DELETING, koyeb.SERVICESTATUS_PAUSING:
break
default:
return nil
}
}
}

log.Infof("Service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb service logs %s -t build`. For the runtime logs, run `koyeb service logs %s`",
res.Service.GetId()[:8],
res.Service.GetId()[:8],
)
return nil
return fmt.Errorf("service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb service logs %s -t build`. For the runtime logs, run `koyeb service logs %s`",
res.Service.GetId()[:8],
res.Service.GetId()[:8],
)
}

return nil
Expand Down
23 changes: 15 additions & 8 deletions pkg/koyeb/services_redeploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func (h *ServiceHandler) ReDeploy(ctx *CLIContext, cmd *cobra.Command, args []st
useCache := GetBoolFlags(cmd, "use-cache")
skipBuild := GetBoolFlags(cmd, "skip-build")
wait := GetBoolFlags(cmd, "wait")
waitTimeout := GetDurationFlags(cmd, "wait-timeout")

redeployBody := *koyeb.NewRedeployRequestInfoWithDefaults()
redeployBody.UseCache = &useCache
Expand All @@ -44,7 +45,7 @@ func (h *ServiceHandler) ReDeploy(ctx *CLIContext, cmd *cobra.Command, args []st
)

if wait {
ctxd, cancel := context.WithTimeout(ctx.Context, 5*time.Minute)
ctxd, cancel := context.WithTimeout(ctx.Context, waitTimeout)
defer cancel()

for range ticker(ctxd, 2*time.Second) {
Expand All @@ -57,20 +58,26 @@ func (h *ServiceHandler) ReDeploy(ctx *CLIContext, cmd *cobra.Command, args []st
)
}

if res.Deployment != nil && res.Deployment.Status != nil &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_ALLOCATING &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_PROVISIONING &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_PENDING &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_STARTING {
return nil
if res.Deployment != nil && res.Deployment.Status != nil {
switch status := *res.Deployment.Status; status {
case koyeb.DEPLOYMENTSTATUS_ERROR, koyeb.DEPLOYMENTSTATUS_DEGRADED, koyeb.DEPLOYMENTSTATUS_UNHEALTHY, koyeb.DEPLOYMENTSTATUS_CANCELED, koyeb.DEPLOYMENTSTATUS_STOPPED, koyeb.DEPLOYMENTSTATUS_ERRORING:
return fmt.Errorf("Deployment %s update ended in status: %s", res.Deployment.GetId()[:8], status)
case koyeb.DEPLOYMENTSTATUS_STARTING, koyeb.DEPLOYMENTSTATUS_PENDING, koyeb.DEPLOYMENTSTATUS_PROVISIONING, koyeb.DEPLOYMENTSTATUS_ALLOCATING:
break
default:
return nil
}
}
}

log.Infof("Service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb deployment logs %s -t build`. For the runtime logs, run `koyeb deployment logs %s`",
res.Deployment.GetId()[:8],
res.Deployment.GetId()[:8],
)
return nil
return fmt.Errorf("service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb deployment logs %s -t build`. For the runtime logs, run `koyeb deployment logs %s`",
res.Deployment.GetId()[:8],
res.Deployment.GetId()[:8],
)
}

log.Infof("Service %s redeployed.", serviceName)
Expand Down
27 changes: 18 additions & 9 deletions pkg/koyeb/services_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/spf13/cobra"
)

func (h *ServiceHandler) Update(ctx *CLIContext, cmd *cobra.Command, args []string, updateService *koyeb.UpdateService, wait bool) error {
func (h *ServiceHandler) Update(ctx *CLIContext, cmd *cobra.Command, args []string, updateService *koyeb.UpdateService) error {
serviceName, err := h.parseServiceName(cmd, args[0])
if err != nil {
return err
Expand All @@ -22,6 +22,9 @@ func (h *ServiceHandler) Update(ctx *CLIContext, cmd *cobra.Command, args []stri
return err
}

wait, _ := cmd.Flags().GetBool("wait")
waitTimeout, _ := cmd.Flags().GetDuration("wait-timeout")

res, resp, err := ctx.Client.ServicesApi.UpdateService(ctx.Context, service).Service(*updateService).Execute()
if err != nil {
return errors.NewCLIErrorFromAPIError(
Expand All @@ -47,7 +50,7 @@ func (h *ServiceHandler) Update(ctx *CLIContext, cmd *cobra.Command, args []stri
}()

if wait {
ctxd, cancel := context.WithTimeout(ctx.Context, 5*time.Minute)
ctxd, cancel := context.WithTimeout(ctx.Context, waitTimeout)
defer cancel()

for range ticker(ctxd, 2*time.Second) {
Expand All @@ -60,20 +63,26 @@ func (h *ServiceHandler) Update(ctx *CLIContext, cmd *cobra.Command, args []stri
)
}

if res.Deployment != nil && res.Deployment.Status != nil &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_ALLOCATING &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_PROVISIONING &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_PENDING &&
*res.Deployment.Status != koyeb.DEPLOYMENTSTATUS_STARTING {
return nil
if res.Deployment != nil && res.Deployment.Status != nil {
switch status := *res.Deployment.Status; status {
case koyeb.DEPLOYMENTSTATUS_ERROR, koyeb.DEPLOYMENTSTATUS_DEGRADED, koyeb.DEPLOYMENTSTATUS_UNHEALTHY, koyeb.DEPLOYMENTSTATUS_CANCELED, koyeb.DEPLOYMENTSTATUS_STOPPED, koyeb.DEPLOYMENTSTATUS_ERRORING:
return fmt.Errorf("Deployment %s update ended in status: %s", res.Deployment.GetId()[:8], status)
case koyeb.DEPLOYMENTSTATUS_STARTING, koyeb.DEPLOYMENTSTATUS_PENDING, koyeb.DEPLOYMENTSTATUS_PROVISIONING, koyeb.DEPLOYMENTSTATUS_ALLOCATING:
break
default:
return nil
}
}
}

log.Infof("Service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb service logs %s -t build`. For the runtime logs, run `koyeb service logs %s`",
res.Service.GetId()[:8],
res.Service.GetId()[:8],
)
return nil
return fmt.Errorf("service deployment still in progress, --wait timed out. To access the build logs, run: `koyeb service logs %s -t build`. For the runtime logs, run `koyeb service logs %s`",
res.Service.GetId()[:8],
res.Service.GetId()[:8],
)
}
return nil
}
Loading