Skip to content

Commit

Permalink
Add --checks-grace-period to service update
Browse files Browse the repository at this point in the history
  • Loading branch information
brmzkw committed Jul 29, 2024
1 parent 1bed53e commit 8a8952d
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## v4.2.1 (unreleased)

* Change the `volumes` subcommand to handle sizes in GB.
* Add `--checks-grace-period` to set the grace period for health checks, for example with `koyeb service update app/service --checks 8000:http:/healtcheck --checks-grace-period 8000=10`.

## v4.2.0

Expand Down
12 changes: 12 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ See examples of koyeb service create --help
For TCP healthchecks, use the format <PORT>:tcp, for example --checks 8080:tcp
To delete a healthcheck, use !PORT, for example --checks '!8080'
--checks-grace-period strings Set healthcheck grace period in seconds.
Use the format <healthcheck>=<seconds>, for example --checks-grace-period 8080=10
--docker string Docker image
--docker-args strings Set arguments to the docker command. To provide multiple arguments, use the --docker-args flag multiple times.
--docker-command string Set the docker CMD explicitly. To provide arguments to the command, use the --docker-args flag.
Expand Down Expand Up @@ -568,6 +571,9 @@ koyeb deploy <path> <app>/<service> [flags]
For TCP healthchecks, use the format <PORT>:tcp, for example --checks 8080:tcp
To delete a healthcheck, use !PORT, for example --checks '!8080'
--checks-grace-period strings Set healthcheck grace period in seconds.
Use the format <healthcheck>=<seconds>, for example --checks-grace-period 8080=10
--env strings Update service environment variables using the format KEY=VALUE, for example --env FOO=bar
To use the value of a secret as an environment variable, specify the secret name preceded by @, for example --env FOO=@bar
To delete an environment variable, prefix its name with '!', for example --env '!FOO'
Expand Down Expand Up @@ -1364,6 +1370,9 @@ $> koyeb service create myservice --app myapp --docker nginx --port 80:tcp
For TCP healthchecks, use the format <PORT>:tcp, for example --checks 8080:tcp
To delete a healthcheck, use !PORT, for example --checks '!8080'
--checks-grace-period strings Set healthcheck grace period in seconds.
Use the format <healthcheck>=<seconds>, for example --checks-grace-period 8080=10
--docker string Docker image
--docker-args strings Set arguments to the docker command. To provide multiple arguments, use the --docker-args flag multiple times.
--docker-command string Set the docker CMD explicitly. To provide arguments to the command, use the --docker-args flag.
Expand Down Expand Up @@ -1779,6 +1788,9 @@ $> koyeb service update myapp/myservice --port 80:tcp --route '!/'
For TCP healthchecks, use the format <PORT>:tcp, for example --checks 8080:tcp
To delete a healthcheck, use !PORT, for example --checks '!8080'
--checks-grace-period strings Set healthcheck grace period in seconds.
Use the format <healthcheck>=<seconds>, for example --checks-grace-period 8080=10
--docker string Docker image
--docker-args strings Set arguments to the docker command. To provide multiple arguments, use the --docker-args flag multiple times.
--docker-command string Set the docker CMD explicitly. To provide arguments to the command, use the --docker-args flag.
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/koyeb/koyeb-api-client-go v0.0.0-20240614093523-e18bb900e4f4 h1:5B1SXB8vrUXKOQ1cZdzdNcMsSzhILwfPchk9UKel3s8=
github.com/koyeb/koyeb-api-client-go v0.0.0-20240614093523-e18bb900e4f4/go.mod h1:+oQfFj2WL3gi9Pb+UHbob4D7xaT52mPfKyH1UvWa4PQ=
github.com/koyeb/koyeb-api-client-go v0.0.0-20240626143115-aa41e51698e2 h1:5g98xW8nXOiZ6NPbJdbBXSA7ZCrrlfXhB0Ymw2sVZA0=
github.com/koyeb/koyeb-api-client-go v0.0.0-20240626143115-aa41e51698e2/go.mod h1:+oQfFj2WL3gi9Pb+UHbob4D7xaT52mPfKyH1UvWa4PQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
Expand Down
176 changes: 170 additions & 6 deletions pkg/koyeb/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package koyeb
import (
"fmt"
"regexp"
"strconv"
"strings"

"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
Expand Down Expand Up @@ -346,6 +347,12 @@ func (h *ServiceHandler) addServiceDefinitionFlagsForAllSources(flags *pflag.Fla
"For TCP healthchecks, use the format <PORT>:tcp, for example --checks 8080:tcp\n"+
"To delete a healthcheck, use !PORT, for example --checks '!8080'\n",
)
flags.StringSlice(
"checks-grace-period",
nil,
"Set healthcheck grace period in seconds.\n"+
"Use the format <healthcheck>=<seconds>, for example --checks-grace-period 8080=10\n",
)
flags.StringSlice(
"volumes",
nil,
Expand All @@ -356,12 +363,25 @@ func (h *ServiceHandler) addServiceDefinitionFlagsForAllSources(flags *pflag.Fla
// Configure aliases: for example, allow user to use --port instead of --ports
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
aliases := map[string]string{
"port": "ports",
"check": "checks",
"healthcheck": "checks",
"health-check": "checks",
"healthchecks": "checks",
"health-checks": "checks",
"port": "ports",
"check": "checks",

"healthcheck": "checks",
"healthcheck-grace": "checks-grace-period",
"healthcheck-grace-period": "checks-grace-period",

"health-check": "checks",
"health-check-grace": "checks-grace-period",
"health-check-grace-period": "checks-grace-period",

"healthchecks": "checks",
"healthchecks-grace": "checks-grace-period",
"healthchecks-grace-period": "checks-grace-period",

"health-checks": "checks",
"health-checks-graee": "checks-grace-period",
"health-checks-grace-period": "checks-grace-period",

"route": "routes",
"volume": "volumes",
"region": "regions",
Expand Down Expand Up @@ -729,9 +749,153 @@ func (h *ServiceHandler) parseChecks(type_ koyeb.DeploymentDefinitionType, flags
Solution: "Fix the service type or remove the healthchecks from your service, and try again",
}
}

checksGracePeriod, _ := flags.GetStringSlice("checks-grace-period")
for _, val := range checksGracePeriod {
if err := h.parseChecksGracePeriod(newChecks, val); err != nil {
return nil, err
}
}
return newChecks, nil
}

// parseChecksGracePeriod parses the --checks-grace-period flag and updates the healthchecks with the specified grace period.
func (h *ServiceHandler) parseChecksGracePeriod(checks []koyeb.DeploymentHealthCheck, grace string) error {
parts := strings.Split(grace, "=")
if len(parts) != 2 {
return &errors.CLIError{
What: "Invalid grace period",
Why: "--checks-grace-period should be formatted as <check>=<seconds>",
Additional: []string{
"If unambiguous, <check> can be the port number of the healthcheck",
"If several checks are defined on the same port, provide the full definition for the check, for example 8080:tcp or 8080:http or 8080:http:/healthcheck",
},
Orig: nil,
Solution: "Provide a valid grace period and try again",
}
}

graceValue, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
return &errors.CLIError{
What: "Invalid grace period",
Why: fmt.Sprintf("the grace period should be a number of seconds, not %s", parts[1]),
Additional: nil,
Orig: nil,
Solution: "Provide a valid grace period and try again",
}
}

var match *koyeb.DeploymentHealthCheck
// Find a healthcheck matching the identifier provided in the --checks-grace-period flag
for idx := range checks {
equal, err := h.healthcheckEqual(parts[0], checks[idx])
if err != nil {
return err
}
if equal && match != nil {
return &errors.CLIError{
What: "Ambiguous grace period",
Why: `--checks-grace-period matches multiple healthchecks`,
Additional: []string{
fmt.Sprintf("The value %s matches several healthchecks", grace),
"Provide a more specific identifier, for example 8080:http:/healthcheck or 8080:tcp",
},
Orig: nil,
Solution: "Provide an unambiguous identifier and try again",
}
}
if equal {
match = &checks[idx]
}
}
if match == nil {
return &errors.CLIError{
What: "Invalid grace period",
Why: "--checks-grace-period does not match any healthcheck",
Additional: []string{
"The flag --checks-grace-period has been specified, but no healthcheck matches the identifier",
},
Orig: nil,
Solution: "Fix the flag --checks-grace-period to match an existing healthcheck or remove the grace period, and try again",
}
}
(*match).GracePeriod = &graceValue
return nil
}

// Returns true if the string "candidate" is equal to "healthcheck". The string
// "a" is a dotted string representation of a healthcheck, for example 8000,
// 8000:tcp, 8000:http or 8000:http:/path.
func (h *ServiceHandler) healthcheckEqual(candidate string, healthcheck koyeb.DeploymentHealthCheck) (bool, error) {
parts := strings.Split(candidate, ":")

if len(parts) == 0 {
return false, &errors.CLIError{
What: "Invalid grace period",
Why: "the healthcheck identifier should start with a port number",
Additional: nil,
Orig: nil,
Solution: "Fix the flag --checks-grace-period to match an existing healthcheck or remove the grace period, and try again",
}
}

port, err := strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return false, &errors.CLIError{
What: "Invalid grace period",
Why: "the healthcheck identifier should start with a port number",
Additional: []string{
"The flag --checks-grace-period has been specified, but no healthcheck matches the identifier",
},
Orig: nil,
Solution: "Fix the flag --checks-grace-period to match an existing healthcheck or remove the grace period, and try again",
}
}

switch {
case healthcheck.HasHttp():
switch len(parts) {
case 1:
return port == *healthcheck.GetHttp().Port, nil
case 2:
return port == *healthcheck.GetHttp().Port && strings.ToLower(parts[1]) == "http", nil
case 3:
return port == *healthcheck.GetHttp().Port && strings.ToLower(parts[1]) == "http" && parts[2] == *healthcheck.GetHttp().Path, nil
default:
return false, &errors.CLIError{
What: "Invalid grace period",
Why: "the healthcheck identifier is invalid",
Additional: []string{
"For TCP healtchecks, use the format <PORT>:tcp, for example --checks 8080:tcp",
"For HTTP healthchecks, use the format <PORT>:http, for example 8080, <PORT>:http or <PORT>:http:<PATH>, for example --checks 8080:http:/health",
},
Orig: nil,
Solution: "Fix the flag --checks-grace-period to match an existing healthcheck or remove the grace period, and try again",
}
}
case healthcheck.HasTcp():
switch len(parts) {
case 1:
return port == *healthcheck.GetTcp().Port, nil
case 2:
return port == *healthcheck.GetTcp().Port && strings.ToLower(parts[1]) == "tcp", nil
default:
return false, &errors.CLIError{
What: "Invalid grace period",
Why: "the healthcheck identifier is invalid",
Additional: []string{
"For TCP healtchecks, use the format <PORT>:tcp, for example --checks 8080:tcp",
"For HTTP healthchecks, use the format <PORT>:http, for example 8080, <PORT>:http or <PORT>:http:<PATH>, for example --checks 8080:http:/health",
},
Orig: nil,
Solution: "Fix the flag --checks-grace-period to match an existing healthcheck or remove the grace period, and try again",
}
}
}
return false, nil
}

// Parse --regions
func (h *ServiceHandler) parseRegions(flags *pflag.FlagSet, currentRegions []string) ([]string, error) {
newRegions, err := parseListFlags("regions", flags_list.NewRegionsListFromFlags, flags, currentRegions)
Expand Down

0 comments on commit 8a8952d

Please sign in to comment.