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

Support Probe-level terminationGracePeriodSeconds #2697

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
85 changes: 85 additions & 0 deletions kubernetes/resource_kubernetes_deployment_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,38 @@ func TestAccKubernetesDeploymentV1_with_container_liveness_probe_using_tcp(t *te
})
}

func TestAccKubernetesDeploymentV1_with_container_liveness_probe_using_termination_grace_period_seconds(t *testing.T) {
var conf appsv1.Deployment

deploymentName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resourceName := "kubernetes_deployment_v1.test"
imageName := agnhostImage

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckKubernetesDeploymentV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesDeploymentV1ConfigWithLivenessProbeUsingTerminationGracePeriodSeconds(deploymentName, imageName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesDeploymentV1Exists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.args.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.http_get.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.http_get.0.path", "/healthz"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.http_get.0.port", "8080"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.http_get.0.http_header.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.http_get.0.http_header.0.name", "X-Custom-Header"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.http_get.0.http_header.0.value", "Awesome"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.initial_delay_seconds", "3"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.liveness_probe.0.termination_grace_period_seconds", "10"),
),
},
},
})
}

func TestAccKubernetesDeploymentV1_with_container_lifecycle(t *testing.T) {
var conf appsv1.Deployment

Expand Down Expand Up @@ -2012,6 +2044,59 @@ func testAccKubernetesDeploymentV1ConfigWithLivenessProbeUsingHTTPGet(deployment
`, deploymentName, imageName)
}

func testAccKubernetesDeploymentV1ConfigWithLivenessProbeUsingTerminationGracePeriodSeconds(deploymentName, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_deployment_v1" "test" {
metadata {
name = "%s"

labels = {
Test = "TfAcceptanceTest"
}
}

spec {
selector {
match_labels = {
Test = "TfAcceptanceTest"
}
}

template {
metadata {
labels = {
Test = "TfAcceptanceTest"
}
}

spec {
container {
image = "%s"
name = "containername"
args = ["liveness"]

liveness_probe {
http_get {
path = "/healthz"
port = 8080

http_header {
name = "X-Custom-Header"
value = "Awesome"
}
}
initial_delay_seconds = 3
period_seconds = 1
termination_grace_period_seconds = 10
}
}
termination_grace_period_seconds = 1
}
}
}
}
`, deploymentName, imageName)
}

func testAccKubernetesDeploymentV1ConfigWithLivenessProbeUsingTCP(deploymentName, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_deployment_v1" "test" {
metadata {
Expand Down
141 changes: 141 additions & 0 deletions kubernetes/resource_kubernetes_pod_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,47 @@ func TestAccKubernetesPodV1_with_container_liveness_probe_using_http_get(t *test
})
}

func TestAccKubernetesPodV1_with_container_liveness_probe_using_termination_grace_period_seconds(t *testing.T) {
var conf api.Pod

podName := acctest.RandomWithPrefix("tf-acc-test")
imageName := agnhostImage
resourceName := "kubernetes_pod_v1.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
skipIfClusterVersionLessThan(t, "1.25.0")
},
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckKubernetesPodV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesPodV1ConfigWithLivenessProbeUsingTerminationGracePeriod(podName, imageName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesPodV1Exists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.args.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.http_get.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.http_get.0.path", "/healthz"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.http_get.0.port", "8080"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.http_get.0.http_header.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.http_get.0.http_header.0.name", "X-Custom-Header"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.http_get.0.http_header.0.value", "Awesome"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.initial_delay_seconds", "3"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.liveness_probe.0.termination_grace_period_seconds", "10"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"metadata.0.resource_version"},
},
},
})
}

func TestAccKubernetesPodV1_with_container_liveness_probe_using_tcp(t *testing.T) {
var conf api.Pod

Expand Down Expand Up @@ -1188,6 +1229,43 @@ func TestAccKubernetesPodV1_config_container_startup_probe(t *testing.T) {
})
}

func TestAccKubernetesPodV1_config_container_startup_probe_termination_grace_period_seconds(t *testing.T) {
var confPod api.Pod

podName := acctest.RandomWithPrefix("tf-acc-test")
imageName := busyboxImage
resourceName := "kubernetes_pod_v1.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
skipIfClusterVersionLessThan(t, "1.25.0")
},
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckKubernetesPodV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesPodV1ContainerStartupProbe_termination_grace_period_seconds(podName, imageName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesPodV1Exists(resourceName, &confPod),
resource.TestCheckResourceAttr(resourceName, "metadata.0.generation", "0"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.startup_probe.0.http_get.0.path", "/index.html"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.startup_probe.0.http_get.0.port", "80"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.startup_probe.0.initial_delay_seconds", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.startup_probe.0.timeout_seconds", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.container.0.startup_probe.0.termination_grace_period_seconds", "10"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"metadata.0.resource_version"},
},
},
})
}

func TestAccKubernetesPodV1_termination_message_policy_default(t *testing.T) {
var confPod api.Pod

Expand Down Expand Up @@ -2117,6 +2195,42 @@ func testAccKubernetesPodV1ConfigWithLivenessProbeUsingHTTPGet(podName, imageNam
`, podName, imageName)
}

func testAccKubernetesPodV1ConfigWithLivenessProbeUsingTerminationGracePeriod(podName, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_pod_v1" "test" {
metadata {
labels = {
app = "pod_label"
}

name = "%s"
}

spec {
container {
image = "%s"
name = "containername"
args = ["liveness"]

liveness_probe {
http_get {
path = "/healthz"
port = 8080

http_header {
name = "X-Custom-Header"
value = "Awesome"
}
}
initial_delay_seconds = 3
period_seconds = 1
termination_grace_period_seconds = 10
}
}
}
}
`, podName, imageName)
}

func testAccKubernetesPodV1ConfigWithLivenessProbeUsingTCP(podName, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_pod_v1" "test" {
metadata {
Expand Down Expand Up @@ -2946,6 +3060,33 @@ func testAccKubernetesPodV1ContainerStartupProbe(podName, imageName string) stri
`, podName, imageName)
}

func testAccKubernetesPodV1ContainerStartupProbe_termination_grace_period_seconds(podName, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_pod_v1" "test" {
metadata {
name = "%s"
}

spec {
container {
image = "%s"
name = "containername"

startup_probe {
http_get {
path = "/index.html"
port = 80
}

initial_delay_seconds = 1
timeout_seconds = 2
termination_grace_period_seconds = 10
}
}
}
}
`, podName, imageName)
}

func testAccKubernetesTerminationMessagePolicyDefault(podName, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_pod_v1" "test" {
metadata {
Expand Down
33 changes: 28 additions & 5 deletions kubernetes/schema_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ func containerFields(isUpdatable bool) map[string]*schema.Schema {
MaxItems: 1,
ForceNew: !isUpdatable,
Description: "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes",
Elem: probeSchema(),
Elem: probeSchema(LivenessProbe),
},
"name": {
Type: schema.TypeString,
Expand Down Expand Up @@ -589,7 +589,7 @@ func containerFields(isUpdatable bool) map[string]*schema.Schema {
MaxItems: 1,
ForceNew: !isUpdatable,
Description: "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes",
Elem: probeSchema(),
Elem: probeSchema(ReadinessProbe),
},
"resources": {
Type: schema.TypeList,
Expand All @@ -616,7 +616,7 @@ func containerFields(isUpdatable bool) map[string]*schema.Schema {
MaxItems: 1,
ForceNew: !isUpdatable,
Description: "StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. This is an alpha feature enabled by the StartupProbe feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes",
Elem: probeSchema(),
Elem: probeSchema(StartupProbe),
},
"stdin": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -685,7 +685,18 @@ func containerFields(isUpdatable bool) map[string]*schema.Schema {
return s
}

func probeSchema() *schema.Resource {
type ProbeType int

const (
LivenessProbe ProbeType = iota
ReadinessProbe
StartupProbe
)

// probeSchema generates the schema for a Liveness/Readiness/Startup probe
//
// probeType specifies the type of probe to generate the schema for (liveness, readiness, startup)
func probeSchema(probeType ProbeType) *schema.Resource {
h := lifecycleHandlerFields()
h["grpc"] = &schema.Schema{
Type: schema.TypeList,
Expand Down Expand Up @@ -741,10 +752,22 @@ func probeSchema() *schema.Resource {
ValidateFunc: validatePositiveInteger,
Description: "Number of seconds after which the probe times out. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes",
}

// Conditionally add a `termination_grace_period_seconds` to this schema if the probe type is Liveness or Startup
// `Probe-level terminationGracePeriodSeconds cannot be set for readiness probes. It will be rejected by the API server.`
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#probe-level-terminationgraceperiodseconds
if probeType == LivenessProbe || probeType == StartupProbe {
h["termination_grace_period_seconds"] = &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validateIntGreaterThan(0),
Description: "Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's `terminationGracePeriodSeconds` will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer greater than zero. `spec.terminationGracePeriodSeconds` is used if unset.",
}
}

return &schema.Resource{
Schema: h,
}

}

func securityContextSchema(isUpdatable bool) *schema.Resource {
Expand Down
8 changes: 8 additions & 0 deletions kubernetes/structures_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ func flattenProbe(in *v1.Probe) []interface{} {
if in.GRPC != nil {
att["grpc"] = flattenGRPC(in.GRPC)
}
if in.TerminationGracePeriodSeconds != nil {
att["termination_grace_period_seconds"] = *in.TerminationGracePeriodSeconds
}

return []interface{}{att}
}
Expand Down Expand Up @@ -765,6 +768,11 @@ func expandProbe(l []interface{}) *v1.Probe {
obj.TimeoutSeconds = int32(v)
}

// Set the `TerminationGracePeriodSeconds` field only if it is not set to the default (0)
if v, ok := in["termination_grace_period_seconds"].(int); ok && v != 0 {
obj.TerminationGracePeriodSeconds = ptr.To(int64(v))
}

return &obj
}

Expand Down