Skip to content

Commit

Permalink
Add PodFailurePolicy for Jobs (#2394)
Browse files Browse the repository at this point in the history
* initial schema creation

* add expander functions

* add flattener functions

* WIP: podFailurePolicy tests

* finish pod_failure_policy in jobs

* remove typo

* add changelog-entry

* add cronjob podFailurePolicy test

* add requested changes

* changelog-entry

* fix lints

* add podFailurePolicy to uJobv1_update test

* fix jobv1_update test

* lint-fix

* add value check in tests cronjobs/jobs

* add value check

* update cronjobs/jobs docs

* update  in docs to be required

* use TypeList for values field in on_exit_codes

* WIP: values is empty in fflatten function

* fix flatten/expand of exit codes list

* remove sort function

* update values in tests to be in order

* update docs / remove unused functions

* add pod-failure-policy to patchSpec function

* path typo

* forceNew

* add TestAccKubernetesCronJobV1_minimalWithPodFailurePolicy

* tests-lint-fix

---------

Co-authored-by: John Houston <jhouston@hashicorp.com>
  • Loading branch information
BBBmau and jrhouston authored Jan 30, 2024
1 parent 4db62a7 commit f580087
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .changelog/2394.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
`resource/kubernetes_job_v1`: add new attribute `spec.pod_failure_policy` to job spec
```
101 changes: 96 additions & 5 deletions kubernetes/resource_kubernetes_cron_job_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,6 @@ func TestAccKubernetesCronJobV1_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "spec.0.starting_deadline_seconds", "0"),
resource.TestCheckResourceAttr(resourceName, "spec.0.successful_jobs_history_limit", "3"),
resource.TestCheckResourceAttr(resourceName, "spec.0.suspend", "false"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.parallelism", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.backoff_limit", "6"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.template.0.spec.0.container.0.name", "hello"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.template.0.metadata.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.template.0.metadata.0.labels.%", "1"),
testAccCheckKubernetesCronJobV1ForceNew(&conf1, &conf2, false),
),
},
Expand Down Expand Up @@ -174,6 +169,56 @@ func TestAccKubernetesCronJobV1_minimalWithTemplateNamespace(t *testing.T) {
})
}

func TestAccKubernetesCronJobV1_minimalWithPodFailurePolicy(t *testing.T) {
var conf1, conf2 batchv1.CronJob

name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resourceName := "kubernetes_cron_job_v1.test"
imageName := busyboxImage

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: resourceName,
IDRefreshIgnore: []string{"metadata.0.resource_version"},
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckKubernetesCronJobV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesCronJobV1ConfigMinimal(name, imageName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesCronJobV1Exists(resourceName, &conf1),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.namespace"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.metadata.0.namespace", ""),
),
},
{
Config: testAccKubernetesCronJobV1ConfigMinimalWithPodFailurePolicy(name, imageName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesCronJobV1Exists(resourceName, &conf2),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.generation"),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.uid"),
resource.TestCheckResourceAttrSet(resourceName, "metadata.0.namespace"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.#", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.0.action", "FailJob"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.container_name", "test"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.#", "3"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.0", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.1", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.2", "42"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.1.action", "Ignore"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.1.on_pod_condition.0.type", "DisruptionTarget"),
resource.TestCheckResourceAttr(resourceName, "spec.0.job_template.0.spec.0.pod_failure_policy.0.rule.1.on_pod_condition.0.status", "False"),
testAccCheckKubernetesCronJobV1ForceNew(&conf1, &conf2, true),
),
},
},
})
}

func testAccCheckKubernetesCronJobV1Destroy(s *terraform.State) error {
conn, err := testAccProvider.Meta().(KubeClientsets).MainClientset()

Expand Down Expand Up @@ -413,6 +458,52 @@ func testAccKubernetesCronJobV1ConfigMinimal(name, imageName string) string {
`, name, imageName)
}

func testAccKubernetesCronJobV1ConfigMinimalWithPodFailurePolicy(name, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_cron_job_v1" "test" {
metadata {
name = "%s"
}
spec {
schedule = "*/1 * * * *"
job_template {
metadata {}
spec {
pod_failure_policy {
rule {
action = "FailJob"
on_exit_codes {
container_name = "test"
operator = "In"
values = [1, 2, 42]
}
}
rule {
action = "Ignore"
on_pod_condition {
status = "False"
type = "DisruptionTarget"
}
}
}
template {
metadata {}
spec {
container {
name = "test"
image = "%s"
command = ["sleep", "5"]
}
termination_grace_period_seconds = 1
}
}
}
}
}
}
`, name, imageName)
}

func testAccKubernetesCronJobV1ConfigMinimalWithJobTemplateNamespace(name, imageName string) string {
return fmt.Sprintf(`resource "kubernetes_cron_job_v1" "test" {
metadata {
Expand Down
54 changes: 54 additions & 0 deletions kubernetes/resource_kubernetes_job_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ func TestAccKubernetesJobV1_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.name", "hello"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.image", imageName),
resource.TestCheckResourceAttr(resourceName, "wait_for_completion", "false"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.#", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.action", "FailJob"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.container_name", "hello"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.#", "3"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.0", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.1", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.2", "42"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.1.action", "Ignore"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.1.on_pod_condition.0.type", "DisruptionTarget"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.1.on_pod_condition.0.status", "False"),
),
},
{
Expand Down Expand Up @@ -131,6 +141,16 @@ func TestAccKubernetesJobV1_update(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.spec.0.container.0.image", imageName),
resource.TestCheckResourceAttr(resourceName, "wait_for_completion", "false"),
resource.TestCheckResourceAttr(resourceName, "spec.0.manual_selector", "false"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.#", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.action", "FailJob"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.container_name", "hello"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.#", "3"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.0", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.1", "2"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.0.on_exit_codes.0.values.2", "42"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.1.action", "Ignore"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.1.on_pod_condition.0.type", "DisruptionTarget"),
resource.TestCheckResourceAttr(resourceName, "spec.0.pod_failure_policy.0.rule.1.on_pod_condition.0.status", "False"),
),
},
{
Expand Down Expand Up @@ -307,6 +327,23 @@ func testAccKubernetesJobV1Config_basic(name, imageName string) string {
backoff_limit = 10
completions = 4
parallelism = 2
pod_failure_policy {
rule {
action = "FailJob"
on_exit_codes {
container_name = "hello"
operator = "In"
values = [1, 2, 42]
}
}
rule {
action = "Ignore"
on_pod_condition {
status = "False"
type = "DisruptionTarget"
}
}
}
template {
metadata {}
spec {
Expand Down Expand Up @@ -334,6 +371,23 @@ func testAccKubernetesJobV1Config_updateMutableFields(name, imageName, activeDea
completions = 4
manual_selector = %s
parallelism = %s
pod_failure_policy {
rule {
action = "FailJob"
on_exit_codes {
container_name = "hello"
operator = "In"
values = [1, 2, 42]
}
}
rule {
action = "Ignore"
on_pod_condition {
status = "False"
type = "DisruptionTarget"
}
}
}
template {
metadata {}
spec {
Expand Down
66 changes: 66 additions & 0 deletions kubernetes/schema_job_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,72 @@ func jobSpecFields(specUpdatable bool) map[string]*schema.Schema {
ValidateFunc: validateNonNegativeInteger,
Description: "Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
},
"pod_failure_policy": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Description: "Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"rule": {
Type: schema.TypeList,
Description: "A label query over volumes to consider for binding.",
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"action": {
Type: schema.TypeString,
Optional: true,
},
"on_exit_codes": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"container_name": {
Type: schema.TypeString,
Optional: true,
},
"operator": {
Type: schema.TypeString,
Optional: true,
},
"values": {
Type: schema.TypeList,
Required: true,
MinItems: 1,
MaxItems: 255,
Elem: &schema.Schema{Type: schema.TypeInt,
ValidateFunc: validation.IntNotInSlice([]int{0})},
},
},
},
},
"on_pod_condition": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"status": {
Type: schema.TypeString,
Optional: true,
Default: "True",
},
"type": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
},
},
},
},
},
// This field is immutable in Jobs.
"selector": {
Type: schema.TypeList,
Expand Down
Loading

0 comments on commit f580087

Please sign in to comment.