Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apps: add support for termination configuration. #1297

Merged
merged 1 commit into from
Jan 6, 2025
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
112 changes: 112 additions & 0 deletions digitalocean/app/app_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const (
functionComponent appSpecComponentType = "function"
)

// AppSpecTermination is a type constraint for the termination attribute of an app component.
type AppSpecTermination interface {
godo.AppServiceSpecTermination | godo.AppWorkerSpecTermination | godo.AppJobSpecTermination
}

// appSpecSchema returns map[string]*schema.Schema for the App Specification.
// Set isResource to true in order to return a schema with additional attributes
// appropriate for a resource or false for one used with a data-source.
Expand Down Expand Up @@ -494,6 +499,28 @@ func appSpecAutoscalingSchema() map[string]*schema.Schema {
}
}

func appSpecTerminationSchema(component appSpecComponentType) map[string]*schema.Schema {
termination := map[string]*schema.Schema{
"grace_period_seconds": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(1, 600),
Description: "The number of seconds to wait between sending a TERM signal to a container and issuing a KILL which causes immediate shutdown. Default: 120, Minimum 1, Maximum 600.",
},
}

if component == serviceComponent {
termination["drain_seconds"] = &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(1, 110),
Description: "The number of seconds to wait between selecting a container instance for termination and issuing the TERM signal. Selecting a container instance for termination begins an asynchronous drain of new requests on upstream load-balancers. Default: 15 seconds, Minimum 1, Maximum 110.",
}
}

return termination
}

func appSpecComponentBase(componentType appSpecComponentType) map[string]*schema.Schema {
baseSchema := map[string]*schema.Schema{
"name": {
Expand Down Expand Up @@ -648,6 +675,14 @@ func appSpecServicesSchema() *schema.Resource {
Schema: appSpecAutoscalingSchema(),
},
},
"termination": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: appSpecTerminationSchema(serviceComponent),
},
},
}

for k, v := range appSpecComponentBase(serviceComponent) {
Expand Down Expand Up @@ -743,6 +778,14 @@ func appSpecWorkerSchema() *schema.Resource {
Schema: appSpecAutoscalingSchema(),
},
},
"termination": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: appSpecTerminationSchema(workerComponent),
},
},
}

for k, v := range appSpecComponentBase(workerComponent) {
Expand Down Expand Up @@ -791,6 +834,14 @@ func appSpecJobSchema() *schema.Resource {
}, false),
Description: "The type of job and when it will be run during the deployment process.",
},
"termination": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: appSpecTerminationSchema(jobComponent),
},
},
}

for k, v := range appSpecComponentBase(jobComponent) {
Expand Down Expand Up @@ -1856,6 +1907,11 @@ func expandAppSpecServices(config []interface{}) []*godo.AppServiceSpec {
s.Autoscaling = expandAppAutoscaling(autoscaling)
}

termination := service["termination"].([]interface{})
if len(termination) > 0 {
s.Termination = expandAppTermination[godo.AppServiceSpecTermination](termination)
}

appServices = append(appServices, s)
}

Expand Down Expand Up @@ -1889,6 +1945,7 @@ func flattenAppSpecServices(services []*godo.AppServiceSpec) []map[string]interf
r["alert"] = flattenAppAlerts(s.Alerts)
r["log_destination"] = flattenAppLogDestinations(s.LogDestinations)
r["autoscaling"] = flattenAppAutoscaling(s.Autoscaling)
r["termination"] = flattenAppTermination(s.Termination)

result[i] = r
}
Expand Down Expand Up @@ -2039,6 +2096,11 @@ func expandAppSpecWorkers(config []interface{}) []*godo.AppWorkerSpec {
s.Autoscaling = expandAppAutoscaling(autoscaling)
}

termination := worker["termination"].([]interface{})
if len(termination) > 0 {
s.Termination = expandAppTermination[godo.AppWorkerSpecTermination](termination)
}

appWorkers = append(appWorkers, s)
}

Expand Down Expand Up @@ -2067,6 +2129,7 @@ func flattenAppSpecWorkers(workers []*godo.AppWorkerSpec) []map[string]interface
r["alert"] = flattenAppAlerts(w.Alerts)
r["log_destination"] = flattenAppLogDestinations(w.LogDestinations)
r["autoscaling"] = flattenAppAutoscaling(w.Autoscaling)
r["termination"] = flattenAppTermination(w.Termination)

result[i] = r
}
Expand Down Expand Up @@ -2123,6 +2186,11 @@ func expandAppSpecJobs(config []interface{}) []*godo.AppJobSpec {
s.LogDestinations = expandAppLogDestinations(logDestinations)
}

termination := job["termination"].([]interface{})
if len(termination) > 0 {
s.Termination = expandAppTermination[godo.AppJobSpecTermination](termination)
}

appJobs = append(appJobs, s)
}

Expand Down Expand Up @@ -2151,6 +2219,7 @@ func flattenAppSpecJobs(jobs []*godo.AppJobSpec) []map[string]interface{} {
r["kind"] = string(j.Kind)
r["alert"] = flattenAppAlerts(j.Alerts)
r["log_destination"] = flattenAppLogDestinations(j.LogDestinations)
r["termination"] = flattenAppTermination(j.Termination)

result[i] = r
}
Expand Down Expand Up @@ -2445,6 +2514,49 @@ func expandAppIngressMatch(config []interface{}) *godo.AppIngressSpecRuleMatch {
}
}

func expandAppTermination[T AppSpecTermination](config []interface{}) *T {
if len(config) == 0 || config[0] == nil {
return nil
}

terminationConfig := config[0].(map[string]interface{})

termination := new(T)
switch t := any(termination).(type) {
case *godo.AppServiceSpecTermination:
t.GracePeriodSeconds = int32(terminationConfig["grace_period_seconds"].(int))
t.DrainSeconds = int32(terminationConfig["drain_seconds"].(int))
case *godo.AppWorkerSpecTermination:
t.GracePeriodSeconds = int32(terminationConfig["grace_period_seconds"].(int))
case *godo.AppJobSpecTermination:
t.GracePeriodSeconds = int32(terminationConfig["grace_period_seconds"].(int))
}

return termination
}

func flattenAppTermination[T AppSpecTermination](termination *T) []interface{} {
result := make([]interface{}, 0)

if termination != nil {
r := make(map[string]interface{})

switch t := any(termination).(type) {
case *godo.AppServiceSpecTermination:
r["grace_period_seconds"] = t.GracePeriodSeconds
r["drain_seconds"] = t.DrainSeconds
case *godo.AppWorkerSpecTermination:
r["grace_period_seconds"] = t.GracePeriodSeconds
case *godo.AppJobSpecTermination:
r["grace_period_seconds"] = t.GracePeriodSeconds
}

result = append(result, r)
}

return result
}

func flattenAppEgress(egress *godo.AppEgressSpec) []map[string]interface{} {
if egress != nil {
result := make([]map[string]interface{}, 0)
Expand Down
60 changes: 60 additions & 0 deletions digitalocean/app/resource_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,33 @@ func TestAccDigitalOceanApp_ImageDigest(t *testing.T) {
})
}

func TestAccDigitalOceanApp_termination(t *testing.T) {
var app godo.App
appName := acceptance.RandomTestName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.TestAccPreCheck(t) },
Providers: acceptance.TestAccProviders,
CheckDestroy: testAccCheckDigitalOceanAppDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_withTermination, appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.termination.0.grace_period_seconds", "60"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.termination.0.drain_seconds", "30"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.worker.0.termination.0.grace_period_seconds", "30"),
),
},
},
})
}

var testAccCheckDigitalOceanAppConfig_basic = `
resource "digitalocean_app" "foobar" {
spec {
Expand Down Expand Up @@ -1749,3 +1776,36 @@ resource "digitalocean_app" "foobar" {
ingress {}
}
}`

var testAccCheckDigitalOceanAppConfig_withTermination = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "nyc"
service {
name = "go-service"
instance_size_slug = "apps-d-1vcpu-0.5gb"
instance_count = 1
termination {
drain_seconds = 30
grace_period_seconds = 60
}
git {
repo_clone_url = "https://github.com/digitalocean/sample-golang.git"
branch = "main"
}
}
worker {
name = "go-worker"
instance_size_slug = "apps-d-1vcpu-0.5gb"
instance_count = 1
termination {
grace_period_seconds = 30
}
git {
repo_clone_url = "https://github.com/digitalocean/sample-sleeper.git"
branch = "main"
}
}
}
}`
8 changes: 7 additions & 1 deletion docs/data-sources/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ A `service` can contain:
- `password` - Password for user defined in User. Is required when endpoint is set. Cannot be set if using a DigitalOcean DBaaS OpenSearch cluster.
- `index_name` - The index name to use for the logs. If not set, the default index name is `logs`.
- `cluster_name` - The name of a DigitalOcean DBaaS OpenSearch cluster to use as a log forwarding destination. Cannot be specified if endpoint is also specified.
* `termination` - Contains a component's termination parameters.
- `grace_period_seconds` - The number of seconds to wait between sending a TERM signal to a container and issuing a KILL which causes immediate shutdown. Default: 120, Minimum 1, Maximum 600.
- `drain_seconds` - The number of seconds to wait between selecting a container instance for termination and issuing the TERM signal. Selecting a container instance for termination begins an asynchronous drain of new requests on upstream load-balancers. Default: 15 seconds, Minimum 1, Maximum 110.

A `static_site` can contain:

Expand Down Expand Up @@ -201,6 +204,8 @@ A `worker` can contain:
- `metrics` - The metrics that the component is scaled on.
- `cpu` - Settings for scaling the component based on CPU utilization.
- `percent` - The average target CPU utilization for the component.
* `termination` - Contains a component's termination parameters.
- `grace_period_seconds` - The number of seconds to wait between sending a TERM signal to a container and issuing a KILL which causes immediate shutdown. Default: 120, Minimum 1, Maximum 600.

A `job` can contain:

Expand Down Expand Up @@ -257,6 +262,8 @@ A `job` can contain:
- `password` - Password for user defined in User. Is required when endpoint is set. Cannot be set if using a DigitalOcean DBaaS OpenSearch cluster.
- `index_name` - The index name to use for the logs. If not set, the default index name is `logs`.
- `cluster_name` - The name of a DigitalOcean DBaaS OpenSearch cluster to use as a log forwarding destination. Cannot be specified if endpoint is also specified.
* `termination` - Contains a component's termination parameters.
- `grace_period_seconds` - The number of seconds to wait between sending a TERM signal to a container and issuing a KILL which causes immediate shutdown. Default: 120, Minimum 1, Maximum 600.

A `function` component can contain:

Expand Down Expand Up @@ -314,7 +321,6 @@ A `function` component can contain:
- `index_name` - The index name to use for the logs. If not set, the default index name is `logs`.
- `cluster_name` - The name of a DigitalOcean DBaaS OpenSearch cluster to use as a log forwarding destination. Cannot be specified if endpoint is also specified.


A `database` can contain:

* `name` - The name of the component.
Expand Down
7 changes: 7 additions & 0 deletions docs/resources/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ A `service` can contain:
- `metrics` - The metrics that the component is scaled on.
- `cpu` - Settings for scaling the component based on CPU utilization.
- `percent` - The average target CPU utilization for the component.
- `termination` - Contains a component's termination parameters.
- `grace_period_seconds` - The number of seconds to wait between sending a TERM signal to a container and issuing a KILL which causes immediate shutdown. Default: 120, Minimum 1, Maximum 600.
- `drain_seconds` - The number of seconds to wait between selecting a container instance for termination and issuing the TERM signal. Selecting a container instance for termination begins an asynchronous drain of new requests on upstream load-balancers. Default: 15 seconds, Minimum 1, Maximum 110.

A `static_site` can contain:

Expand Down Expand Up @@ -413,6 +416,8 @@ A `worker` can contain:
- `metrics` - The metrics that the component is scaled on.
- `cpu` - Settings for scaling the component based on CPU utilization.
- `percent` - The average target CPU utilization for the component.
- `termination` - Contains a component's termination parameters.
- `grace_period_seconds` - The number of seconds to wait between sending a TERM signal to a container and issuing a KILL which causes immediate shutdown. Default: 120, Minimum 1, Maximum 600.

A `job` can contain:

Expand Down Expand Up @@ -476,6 +481,8 @@ A `job` can contain:
- `password` - Password for user defined in User. Is required when endpoint is set. Cannot be set if using a DigitalOcean DBaaS OpenSearch cluster.
- `index_name` - The index name to use for the logs. If not set, the default index name is `logs`.
- `cluster_name` - The name of a DigitalOcean DBaaS OpenSearch cluster to use as a log forwarding destination. Cannot be specified if endpoint is also specified.
- `termination` - Contains a component's termination parameters.
- `grace_period_seconds` - The number of seconds to wait between sending a TERM signal to a container and issuing a KILL which causes immediate shutdown. Default: 120, Minimum 1, Maximum 600.

A `function` component can contain:

Expand Down
Loading