Skip to content

Commit 9228b68

Browse files
author
Antoine Belluard
committed
feat(container/serverless): add health_check block
1 parent 83cfa19 commit 9228b68

File tree

8 files changed

+3240
-2
lines changed

8 files changed

+3240
-2
lines changed

docs/data-sources/container.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ In addition to all arguments above, the following attributes are exported:
9191

9292
- `deploy` - Boolean indicating whether the container is on a production environment.
9393

94-
- `sandbox` - (Optional) Execution environment of the container.
94+
- `sandbox` - Execution environment of the container.
95+
96+
- `heath_check` - Health check configuration block of the container.
97+
- `http` - HTTP health check configuration.
98+
- `path` - Path to use for the HTTP health check.
99+
- `failure_threshold` - Number of consecutive health check failures before considering the container unhealthy.
100+
- `interval`- Period between health checks (in seconds).
95101

96102
- `status` - The container status.
97103

docs/resources/container.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ The following arguments are supported:
8484

8585
- `sandbox` - (Optional) Execution environment of the container.
8686

87+
- `heath_check` - (Optional) Health check configuration block of the container.
88+
- `http` - HTTP health check configuration.
89+
- `path` - Path to use for the HTTP health check.
90+
- `failure_threshold` - Number of consecutive health check failures before considering the container unhealthy.
91+
- `interval`- Period between health checks (in seconds).
92+
8793
- `port` - (Optional) The port to expose the container.
8894

8995
- `deploy` - (Optional) Boolean indicating whether the container is in a production environment.
@@ -152,4 +158,34 @@ The `memory_limit` (in MB) must correspond with the right amount of vCPU. Refer
152158
| 4096 | 2240 |
153159

154160
~>**Important:** Make sure to select the right resources, as you will be billed based on compute usage over time and the number of Containers executions.
155-
Refer to the [Serverless Containers pricing](https://www.scaleway.com/en/docs/faq/serverless-containers/#prices) for more information.
161+
Refer to the [Serverless Containers pricing](https://www.scaleway.com/en/docs/faq/serverless-containers/#prices) for more information.
162+
163+
## Health check configuration
164+
165+
Custom health checks can be configured on the container.
166+
167+
It's possible to specify the HTTP path that the probe will listen to and the number of failures before considering the container as unhealthy.
168+
During a deployment, if a newly created container fails to pass the health check, the deployment is aborted.
169+
As a result, lowering this value can help to reduce the time it takes to detect a failed deployment.
170+
The period between health checks is also configurable.
171+
172+
Example:
173+
174+
```terraform
175+
resource scaleway_container main {
176+
name = "my-container-02"
177+
namespace_id = scaleway_container_namespace.main.id
178+
179+
health_check {
180+
http {
181+
path = "/ping"
182+
}
183+
failure_threshold = 40
184+
interval = "3s"
185+
}
186+
}
187+
```
188+
189+
~>**Important:** Another probe type can be set to TCP with the API, but currently the SDK has not been updated with this parameter.
190+
This is why the only probe that can be used here is the HTTP probe.
191+
Refer to the [API Reference](https://www.scaleway.com/en/developers/api/serverless-containers/#path-containers-create-a-new-container) for more information.

internal/services/container/container.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
99
container "github.com/scaleway/scaleway-sdk-go/api/container/v1beta1"
1010
"github.com/scaleway/scaleway-sdk-go/scw"
11+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf"
1112
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
1213
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
1314
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
@@ -170,6 +171,44 @@ func ResourceContainer() *schema.Resource {
170171
Description: "Execution environment of the container.",
171172
ValidateDiagFunc: verify.ValidateEnum[container.ContainerSandbox](),
172173
},
174+
"health_check": {
175+
Type: schema.TypeSet,
176+
Optional: true,
177+
Computed: true,
178+
Description: "Health check configuration of the container.",
179+
Elem: &schema.Resource{
180+
Schema: map[string]*schema.Schema{
181+
// TCP has not been implemented yet in the API SDK, that's why the parameter is not in the schema.
182+
// See container.ContainerHealthCheckSpecTCPProbe.
183+
"http": {
184+
Type: schema.TypeSet,
185+
Description: "HTTP health check configuration.",
186+
Required: true,
187+
Elem: &schema.Resource{
188+
Schema: map[string]*schema.Schema{
189+
"path": {
190+
Type: schema.TypeString,
191+
Description: "Path to use for the HTTP health check.",
192+
Required: true,
193+
},
194+
},
195+
},
196+
},
197+
"failure_threshold": {
198+
Type: schema.TypeInt,
199+
Description: "Number of consecutive health check failures before considering the container unhealthy.",
200+
Required: true,
201+
},
202+
"interval": {
203+
Type: schema.TypeString,
204+
Description: "Period between health checks.",
205+
DiffSuppressFunc: dsf.Duration,
206+
ValidateDiagFunc: verify.IsDuration(),
207+
Required: true,
208+
},
209+
},
210+
},
211+
},
173212
// computed
174213
"status": {
175214
Type: schema.TypeString,
@@ -280,6 +319,7 @@ func ResourceContainerRead(ctx context.Context, d *schema.ResourceData, m interf
280319
_ = d.Set("deploy", scw.BoolPtr(*types.ExpandBoolPtr(d.Get("deploy"))))
281320
_ = d.Set("http_option", co.HTTPOption)
282321
_ = d.Set("sandbox", co.Sandbox)
322+
_ = d.Set("health_check", flattenHealthCheck(co.HealthCheck))
283323
_ = d.Set("region", co.Region.String())
284324

285325
return nil
@@ -375,6 +415,16 @@ func ResourceContainerUpdate(ctx context.Context, d *schema.ResourceData, m inte
375415
req.Sandbox = container.ContainerSandbox(d.Get("sandbox").(string))
376416
}
377417

418+
if d.HasChanges("health_check") {
419+
healthCheck := d.Get("health_check")
420+
421+
healthCheckReq, errExpandHealthCheck := expandHealthCheck(healthCheck)
422+
if errExpandHealthCheck != nil {
423+
return diag.FromErr(errExpandHealthCheck)
424+
}
425+
req.HealthCheck = healthCheckReq
426+
}
427+
378428
imageHasChanged := d.HasChanges("registry_sha256")
379429
if imageHasChanged {
380430
req.Redeploy = &imageHasChanged

internal/services/container/container_data_source_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,41 @@ func TestAccDataSourceContainer_Basic(t *testing.T) {
4949
},
5050
})
5151
}
52+
53+
func TestAccDataSourceContainer_HealthCheck(t *testing.T) {
54+
tt := acctest.NewTestTools(t)
55+
defer tt.Cleanup()
56+
57+
resource.ParallelTest(t, resource.TestCase{
58+
PreCheck: func() { acctest.PreCheck(t) },
59+
ProviderFactories: tt.ProviderFactories,
60+
CheckDestroy: isNamespaceDestroyed(tt),
61+
Steps: []resource.TestStep{
62+
{
63+
Config: `
64+
resource scaleway_container_namespace main {}
65+
66+
resource scaleway_container main {
67+
namespace_id = scaleway_container_namespace.main.id
68+
deploy = false
69+
}
70+
71+
data scaleway_container main {
72+
namespace_id = scaleway_container_namespace.main.id
73+
container_id = scaleway_container.main.id
74+
}
75+
`,
76+
Check: resource.ComposeTestCheckFunc(
77+
isContainerPresent(tt, "scaleway_container.main"),
78+
// Check default option returned by the API when you don't specify the health_check block.
79+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.#", "1"),
80+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.0.failure_threshold", "30"),
81+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.0.interval", "10s"),
82+
resource.TestCheckResourceAttr("data.scaleway_container.main", "health_check.#", "1"),
83+
resource.TestCheckResourceAttr("data.scaleway_container.main", "health_check.0.failure_threshold", "30"),
84+
resource.TestCheckResourceAttr("data.scaleway_container.main", "health_check.0.interval", "10s"),
85+
),
86+
},
87+
},
88+
})
89+
}

internal/services/container/container_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,60 @@ func TestAccContainer_Sandbox(t *testing.T) {
400400
})
401401
}
402402

403+
func TestAccContainer_HealthCheck(t *testing.T) {
404+
tt := acctest.NewTestTools(t)
405+
defer tt.Cleanup()
406+
resource.ParallelTest(t, resource.TestCase{
407+
PreCheck: func() { acctest.PreCheck(t) },
408+
ProviderFactories: tt.ProviderFactories,
409+
CheckDestroy: isContainerDestroyed(tt),
410+
Steps: []resource.TestStep{
411+
{
412+
Config: `
413+
resource scaleway_container_namespace main {}
414+
415+
resource scaleway_container main {
416+
namespace_id = scaleway_container_namespace.main.id
417+
deploy = false
418+
}
419+
`,
420+
Check: resource.ComposeTestCheckFunc(
421+
isContainerPresent(tt, "scaleway_container.main"),
422+
// Check default option returned by the API when you don't specify the health_check block.
423+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.#", "1"),
424+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.0.failure_threshold", "30"),
425+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.0.interval", "10s"),
426+
),
427+
},
428+
{
429+
Config: `
430+
resource scaleway_container_namespace main {}
431+
432+
resource scaleway_container main {
433+
namespace_id = scaleway_container_namespace.main.id
434+
deploy = false
435+
436+
health_check {
437+
http {
438+
path = "/test"
439+
}
440+
failure_threshold = 40
441+
interval = "12s"
442+
}
443+
}
444+
`,
445+
Check: resource.ComposeTestCheckFunc(
446+
isContainerPresent(tt, "scaleway_container.main"),
447+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.#", "1"),
448+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.0.http.0.path", "/test"),
449+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.0.failure_threshold", "40"),
450+
resource.TestCheckResourceAttr("scaleway_container.main", "health_check.0.interval", "12s"),
451+
),
452+
},
453+
},
454+
})
455+
}
456+
403457
func isContainerPresent(tt *acctest.TestTools, n string) resource.TestCheckFunc {
404458
return func(state *terraform.State) error {
405459
rs, ok := state.RootModule().Resources[n]

internal/services/container/helpers_container.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,107 @@ func setCreateContainerRequest(d *schema.ResourceData, region scw.Region) (*cont
115115
req.Sandbox = container.ContainerSandbox(sandbox.(string))
116116
}
117117

118+
if healthCheck, ok := d.GetOk("health_check"); ok {
119+
healthCheckReq, errExpandHealthCheck := expandHealthCheck(healthCheck)
120+
if errExpandHealthCheck != nil {
121+
return nil, errExpandHealthCheck
122+
}
123+
req.HealthCheck = healthCheckReq
124+
}
125+
118126
return req, nil
119127
}
120128

129+
func expandHealthCheck(healthCheckSchema interface{}) (*container.ContainerHealthCheckSpec, error) {
130+
healthCheck, ok := healthCheckSchema.(*schema.Set)
131+
if !ok {
132+
return &container.ContainerHealthCheckSpec{}, nil
133+
}
134+
135+
for _, option := range healthCheck.List() {
136+
rawOption, isRawOption := option.(map[string]interface{})
137+
if !isRawOption {
138+
continue
139+
}
140+
141+
healthCheckSpec := &container.ContainerHealthCheckSpec{}
142+
if http, ok := rawOption["http"].(*schema.Set); ok {
143+
healthCheckSpec.HTTP = expendHealthCheckHTTP(http)
144+
}
145+
146+
// Failure threshold is a required field and will be checked by TF.
147+
healthCheckSpec.FailureThreshold = uint32(rawOption["failure_threshold"].(int))
148+
149+
if interval, ok := rawOption["interval"]; ok {
150+
duration, err := types.ExpandDuration(interval)
151+
if err != nil {
152+
return nil, err
153+
}
154+
healthCheckSpec.Interval = scw.NewDurationFromTimeDuration(*duration)
155+
}
156+
157+
return healthCheckSpec, nil
158+
}
159+
160+
return &container.ContainerHealthCheckSpec{}, nil
161+
}
162+
163+
func expendHealthCheckHTTP(healthCheckHTTPSchema interface{}) *container.ContainerHealthCheckSpecHTTPProbe {
164+
healthCheckHTTP, ok := healthCheckHTTPSchema.(*schema.Set)
165+
if !ok {
166+
return &container.ContainerHealthCheckSpecHTTPProbe{}
167+
}
168+
169+
for _, option := range healthCheckHTTP.List() {
170+
rawOption, isRawOption := option.(map[string]interface{})
171+
if !isRawOption {
172+
continue
173+
}
174+
175+
httpProbe := &container.ContainerHealthCheckSpecHTTPProbe{}
176+
if path, ok := rawOption["path"].(string); ok {
177+
httpProbe.Path = path
178+
}
179+
180+
return httpProbe
181+
}
182+
183+
return &container.ContainerHealthCheckSpecHTTPProbe{}
184+
}
185+
186+
func flattenHealthCheck(healthCheck *container.ContainerHealthCheckSpec) interface{} {
187+
if healthCheck == nil {
188+
return nil
189+
}
190+
191+
var interval *time.Duration
192+
if healthCheck.Interval != nil {
193+
interval = healthCheck.Interval.ToTimeDuration()
194+
}
195+
196+
flattenedHealthCheck := []map[string]interface{}(nil)
197+
flattenedHealthCheck = append(flattenedHealthCheck, map[string]interface{}{
198+
"http": flattenHealthCheckHTTP(healthCheck.HTTP),
199+
"failure_threshold": types.FlattenUint32Ptr(&healthCheck.FailureThreshold),
200+
"interval": types.FlattenDuration(interval),
201+
})
202+
203+
return flattenedHealthCheck
204+
}
205+
206+
func flattenHealthCheckHTTP(healthCheckHTTP *container.ContainerHealthCheckSpecHTTPProbe) interface{} {
207+
if healthCheckHTTP == nil {
208+
return nil
209+
}
210+
211+
flattenedHealthCheckHTTP := []map[string]interface{}(nil)
212+
flattenedHealthCheckHTTP = append(flattenedHealthCheckHTTP, map[string]interface{}{
213+
"path": types.FlattenStringPtr(&healthCheckHTTP.Path),
214+
})
215+
216+
return flattenedHealthCheckHTTP
217+
}
218+
121219
func expandContainerSecrets(secretsRawMap interface{}) []*container.Secret {
122220
secretsMap := secretsRawMap.(map[string]interface{})
123221
secrets := make([]*container.Secret, 0, len(secretsMap))

0 commit comments

Comments
 (0)