diff --git a/.changelog/34931.txt b/.changelog/34931.txt new file mode 100644 index 00000000000..8f65b4185b0 --- /dev/null +++ b/.changelog/34931.txt @@ -0,0 +1,3 @@ +`release-note:enhancement +resource/aws_batch_job_definition: Adds ability to define `eks_properties` +``` diff --git a/internal/service/batch/eks_properties.go b/internal/service/batch/eks_properties.go new file mode 100644 index 00000000000..187be96b77a --- /dev/null +++ b/internal/service/batch/eks_properties.go @@ -0,0 +1,387 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package batch + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/batch" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/flex" +) + +const ( + ImagePullPolicyAlways = "Always" + ImagePullPolicyIfNotPresent = "IfNotPresent" + ImagePullPolicyNever = "Never" +) + +func ImagePullPolicy_Values() []string { + return []string{ + ImagePullPolicyAlways, + ImagePullPolicyIfNotPresent, + ImagePullPolicyNever, + } +} + +const ( + DNSPolicyDefault = "Default" + DNSPolicyClusterFirst = "ClusterFirst" + DNSPolicyClusterFirstWithHostNet = "ClusterFirstWithHostNet" +) + +func DNSPolicy_Values() []string { + return []string{ + DNSPolicyDefault, + DNSPolicyClusterFirst, + DNSPolicyClusterFirstWithHostNet, + } +} + +func expandEKSPodProperties(podPropsMap map[string]interface{}) *batch.EksPodProperties { + podProps := &batch.EksPodProperties{} + + if v, ok := podPropsMap["containers"]; ok { + containers := v.([]interface{}) + podProps.Containers = expandContainers(containers) + } + + if v, ok := podPropsMap["dns_policy"].(string); ok && v != "" { + podProps.DnsPolicy = aws.String(v) + } + + if v, ok := podPropsMap["host_network"]; ok { + podProps.HostNetwork = aws.Bool(v.(bool)) + } + if m, ok := podPropsMap["metadata"].([]interface{}); ok && len(m) > 0 { + if v, ok := m[0].(map[string]interface{})["labels"]; ok { + podProps.Metadata = &batch.EksMetadata{} + podProps.Metadata.Labels = flex.ExpandStringMap(v.(map[string]interface{})) + } + } + if v, ok := podPropsMap["service_account_name"].(string); ok && v != "" { + podProps.ServiceAccountName = aws.String(v) + } + if v, ok := podPropsMap["volumes"]; ok { + podProps.Volumes = expandVolumes(v.([]interface{})) + } + + return podProps +} + +func expandContainers(containers []interface{}) []*batch.EksContainer { + var result []*batch.EksContainer + + for _, v := range containers { + containerMap := v.(map[string]interface{}) + container := &batch.EksContainer{} + + if v, ok := containerMap["args"]; ok { + container.Args = flex.ExpandStringList(v.([]interface{})) + } + + if v, ok := containerMap["command"]; ok { + container.Command = flex.ExpandStringList(v.([]interface{})) + } + + if v, ok := containerMap["env"].(*schema.Set); ok && v.Len() > 0 { + env := []*batch.EksContainerEnvironmentVariable{} + for _, e := range v.List() { + environment := &batch.EksContainerEnvironmentVariable{} + environ := e.(map[string]interface{}) + if v, ok := environ["name"].(string); ok && v != "" { + environment.Name = aws.String(v) + } + if v, ok := environ["value"].(string); ok && v != "" { + environment.Value = aws.String(v) + } + env = append(env, environment) + } + container.Env = env + } + + if v, ok := containerMap["image"]; ok { + container.Image = aws.String(v.(string)) + } + if v, ok := containerMap["image_pull_policy"].(string); ok && v != "" { + container.ImagePullPolicy = aws.String(v) + } + + if v, ok := containerMap["name"].(string); ok && v != "" { + container.Name = aws.String(v) + } + if r, ok := containerMap["resources"].([]interface{}); ok && len(r) > 0 { + resources := &batch.EksContainerResourceRequirements{} + res := r[0].(map[string]interface{}) + if v, ok := res["limits"]; ok { + resources.Limits = flex.ExpandStringMap(v.(map[string]interface{})) + } + if v, ok := res["requests"]; ok { + resources.Requests = flex.ExpandStringMap(v.(map[string]interface{})) + } + container.Resources = resources + } + + if s, ok := containerMap["security_context"].([]interface{}); ok && len(s) > 0 { + securityContext := &batch.EksContainerSecurityContext{} + security := s[0].(map[string]interface{}) + if v, ok := security["privileged"]; ok { + securityContext.Privileged = aws.Bool(v.(bool)) + } + if v, ok := security["run_as_user"]; ok { + securityContext.RunAsUser = aws.Int64(int64(v.(int))) + } + if v, ok := security["run_as_group"]; ok { + securityContext.RunAsGroup = aws.Int64(int64(v.(int))) + } + if v, ok := security["read_only_root_file_system"]; ok { + securityContext.ReadOnlyRootFilesystem = aws.Bool(v.(bool)) + } + if v, ok := security["run_as_non_root"]; ok { + securityContext.RunAsNonRoot = aws.Bool(v.(bool)) + } + container.SecurityContext = securityContext + } + if v, ok := containerMap["volume_mounts"]; ok { + container.VolumeMounts = expandVolumeMounts(v.([]interface{})) + } + + result = append(result, container) + } + + return result +} + +func expandVolumes(volumes []interface{}) []*batch.EksVolume { + var result []*batch.EksVolume + for _, v := range volumes { + volume := &batch.EksVolume{} + volumeMap := v.(map[string]interface{}) + if v, ok := volumeMap["name"].(string); ok { + volume.Name = aws.String(v) + } + if e, ok := volumeMap["empty_dir"].([]interface{}); ok && len(e) > 0 { + if empty, ok := e[0].(map[string]interface{}); ok { + volume.EmptyDir = &batch.EksEmptyDir{ + Medium: aws.String(empty["medium"].(string)), + SizeLimit: aws.String(empty["size_limit"].(string)), + } + } + } + if h, ok := volumeMap["host_path"].([]interface{}); ok && len(h) > 0 { + volume.HostPath = &batch.EksHostPath{} + if host, ok := h[0].(map[string]interface{}); ok { + if v, ok := host["path"]; ok { + volume.HostPath.Path = aws.String(v.(string)) + } + } + } + if s, ok := volumeMap["secret"].([]interface{}); ok && len(s) > 0 { + volume.Secret = &batch.EksSecret{} + if secret := s[0].(map[string]interface{}); ok { + if v, ok := secret["secret_name"]; ok { + volume.Secret.SecretName = aws.String(v.(string)) + } + if v, ok := secret["optional"]; ok { + volume.Secret.Optional = aws.Bool(v.(bool)) + } + } + } + result = append(result, volume) + } + + return result +} + +func expandVolumeMounts(volumeMounts []interface{}) []*batch.EksContainerVolumeMount { + var result []*batch.EksContainerVolumeMount + for _, v := range volumeMounts { + volumeMount := &batch.EksContainerVolumeMount{} + volumeMountMap := v.(map[string]interface{}) + if v, ok := volumeMountMap["name"]; ok { + volumeMount.Name = aws.String(v.(string)) + } + if v, ok := volumeMountMap["mount_path"]; ok { + volumeMount.MountPath = aws.String(v.(string)) + } + if v, ok := volumeMountMap["read_only"]; ok { + volumeMount.ReadOnly = aws.Bool(v.(bool)) + } + result = append(result, volumeMount) + } + + return result +} + +func flattenEKSProperties(eksProperties *batch.EksProperties) []interface{} { + var eksPropertiesList []interface{} + if eksProperties == nil { + return eksPropertiesList + } + if v := eksProperties.PodProperties; v != nil { + eksPropertiesList = append(eksPropertiesList, map[string]interface{}{ + "pod_properties": flattenEKSPodProperties(eksProperties.PodProperties), + }) + } + + return eksPropertiesList +} + +func flattenEKSPodProperties(podProperties *batch.EksPodProperties) (tfList []interface{}) { + tfMap := make(map[string]interface{}, 0) + if v := podProperties.Containers; v != nil { + tfMap["containers"] = flattenEKSContainers(v) + } + + if v := podProperties.DnsPolicy; v != nil { + tfMap["dns_policy"] = aws.StringValue(v) + } + + if v := podProperties.HostNetwork; v != nil { + tfMap["host_network"] = aws.BoolValue(v) + } + + if v := podProperties.Metadata; v != nil { + metaData := make([]map[string]interface{}, 0) + if v := v.Labels; v != nil { + metaData = append(metaData, map[string]interface{}{"labels": flex.FlattenStringMap(v)}) + } + tfMap["metadata"] = metaData + } + + if v := podProperties.ServiceAccountName; v != nil { + tfMap["service_account_name"] = aws.StringValue(v) + } + + if v := podProperties.Volumes; v != nil { + tfMap["volumes"] = flattenEKSVolumes(v) + } + + tfList = append(tfList, tfMap) + return tfList +} + +func flattenEKSContainers(containers []*batch.EksContainer) (tfList []interface{}) { + for _, container := range containers { + tfMap := map[string]interface{}{} + + if v := container.Args; v != nil { + tfMap["args"] = flex.FlattenStringList(v) + } + + if v := container.Command; v != nil { + tfMap["command"] = flex.FlattenStringList(v) + } + + if v := container.Env; v != nil { + tfMap["env"] = flattenEKSContainerEnvironmentVariables(v) + } + + if v := container.Image; v != nil { + tfMap["image"] = aws.StringValue(v) + } + + if v := container.ImagePullPolicy; v != nil { + tfMap["image_pull_policy"] = aws.StringValue(v) + } + + if v := container.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := container.Resources; v != nil { + tfMap["resources"] = []map[string]interface{}{{ + "limits": flex.FlattenStringMap(v.Limits), + "requests": flex.FlattenStringMap(v.Requests), + }} + } + + if v := container.SecurityContext; v != nil { + tfMap["security_context"] = []map[string]interface{}{{ + "privileged": aws.BoolValue(v.Privileged), + "run_as_user": aws.Int64Value(v.RunAsUser), + "run_as_group": aws.Int64Value(v.RunAsGroup), + "read_only_root_file_system": aws.BoolValue(v.ReadOnlyRootFilesystem), + "run_as_non_root": aws.BoolValue(v.RunAsNonRoot), + }} + } + + if v := container.VolumeMounts; v != nil { + tfMap["volume_mounts"] = flattenEKSContainerVolumeMounts(v) + } + tfList = append(tfList, tfMap) + } + + return tfList +} + +func flattenEKSContainerEnvironmentVariables(env []*batch.EksContainerEnvironmentVariable) (tfList []interface{}) { + for _, e := range env { + tfMap := map[string]interface{}{} + + if v := e.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := e.Value; v != nil { + tfMap["value"] = aws.StringValue(v) + } + tfList = append(tfList, tfMap) + } + + return tfList +} + +func flattenEKSContainerVolumeMounts(volumeMounts []*batch.EksContainerVolumeMount) (tfList []interface{}) { + for _, v := range volumeMounts { + tfMap := map[string]interface{}{} + + if v := v.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := v.MountPath; v != nil { + tfMap["mount_path"] = aws.StringValue(v) + } + + if v := v.ReadOnly; v != nil { + tfMap["read_only"] = aws.BoolValue(v) + } + tfList = append(tfList, tfMap) + } + + return tfList +} + +func flattenEKSVolumes(volumes []*batch.EksVolume) (tfList []interface{}) { + for _, v := range volumes { + tfMap := map[string]interface{}{} + + if v := v.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := v.EmptyDir; v != nil { + tfMap["empty_dir"] = []map[string]interface{}{{ + "medium": aws.StringValue(v.Medium), + "size_limit": aws.StringValue(v.SizeLimit), + }} + } + + if v := v.HostPath; v != nil { + tfMap["host_path"] = []map[string]interface{}{{ + "path": aws.StringValue(v.Path), + }} + } + + if v := v.Secret; v != nil { + tfMap["secret"] = []map[string]interface{}{{ + "secret_name": aws.StringValue(v.SecretName), + "optional": aws.BoolValue(v.Optional), + }} + } + tfList = append(tfList, tfMap) + } + + return tfList +} diff --git a/internal/service/batch/job_definition.go b/internal/service/batch/job_definition.go index ae68a3c33fc..a3c7afb5726 100644 --- a/internal/service/batch/job_definition.go +++ b/internal/service/batch/job_definition.go @@ -48,9 +48,10 @@ func ResourceJobDefinition() *schema.Resource { Computed: true, }, "container_properties": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"eks_properties", "node_properties"}, StateFunc: func(v interface{}) string { json, _ := structure.NormalizeJsonString(v) return json @@ -69,9 +70,10 @@ func ResourceJobDefinition() *schema.Resource { ValidateFunc: validName, }, "node_properties": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"container_properties", "eks_properties"}, StateFunc: func(v interface{}) string { json, _ := structure.NormalizeJsonString(v) return json @@ -82,6 +84,231 @@ func ResourceJobDefinition() *schema.Resource { }, ValidateFunc: validJobNodeProperties, }, + "eks_properties": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"container_properties", "node_properties"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pod_properties": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "containers": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "args": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "command": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "env": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "image": { + Type: schema.TypeString, + Required: true, + }, + "image_pull_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(ImagePullPolicy_Values(), false), + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "resources": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "limits": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "requests": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "security_context": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "privileged": { + Type: schema.TypeBool, + Optional: true, + }, + "read_only_root_file_system": { + Type: schema.TypeBool, + Optional: true, + }, + "run_as_group": { + Type: schema.TypeInt, + Optional: true, + }, + "run_as_non_root": { + Type: schema.TypeBool, + Optional: true, + }, + "run_as_user": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "volume_mounts": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mount_path": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "read_only": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "dns_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(DNSPolicy_Values(), false), + }, + "host_network": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "metadata": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "service_account_name": { + Type: schema.TypeString, + Optional: true, + }, + "volumes": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "empty_dir": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "medium": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice([]string{"", "Memory"}, true), + }, + "size_limit": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "host_path": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Default: "Default", + }, + "secret": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "secret_name": { + Type: schema.TypeString, + Required: true, + }, + "optional": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, "parameters": { Type: schema.TypeMap, Optional: true, @@ -229,12 +456,27 @@ func resourceJobDefinitionCreate(ctx context.Context, d *schema.ResourceData, me input.ContainerProperties = props } } + + if v, ok := d.GetOk("eks_properties"); ok && len(v.([]interface{})) > 0 { + eksProps := v.([]interface{})[0].(map[string]interface{}) + if podProps, ok := eksProps["pod_properties"].([]interface{}); ok && len(podProps) > 0 { + if aws.StringValue(input.Type) == batch.JobDefinitionTypeContainer { + props := expandEKSPodProperties(podProps[0].(map[string]interface{})) + input.EksProperties = &batch.EksProperties{ + PodProperties: props, + } + } + } + } } if jobDefinitionType == batch.JobDefinitionTypeMultinode { if v, ok := d.GetOk("container_properties"); ok && v != nil { return sdkdiag.AppendErrorf(diags, "No `container_properties` can be specified when `type` is %q", jobDefinitionType) } + if v, ok := d.GetOk("eks_properties"); ok && v != nil { + return sdkdiag.AppendErrorf(diags, "No `eks_properties` can be specified when `type` is %q", jobDefinitionType) + } if v, ok := d.GetOk("node_properties"); ok { props, err := expandJobNodeProperties(v.(string)) @@ -314,6 +556,10 @@ func resourceJobDefinitionRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "setting node_properties: %s", err) } + if err := d.Set("eks_properties", flattenEKSProperties(jobDefinition.EksProperties)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting eks_properties: %s", err) + } + d.Set("name", jobDefinition.JobDefinitionName) d.Set("parameters", aws.StringValueMap(jobDefinition.Parameters)) d.Set("platform_capabilities", aws.StringValueSlice(jobDefinition.PlatformCapabilities)) diff --git a/internal/service/batch/job_definition_test.go b/internal/service/batch/job_definition_test.go index fd997a8627e..35d348b26f8 100644 --- a/internal/service/batch/job_definition_test.go +++ b/internal/service/batch/job_definition_test.go @@ -567,9 +567,11 @@ func TestAccBatchJobDefinition_NodePropertiesupdateForcesNewResource(t *testing. }) } -func TestAccBatchJobDefinition_createTypeContainerWithBothProperties(t *testing.T) { +func TestAccBatchJobDefinition_EKSProperties_basic(t *testing.T) { ctx := acctest.Context(t) + var jd batch.JobDefinition rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_batch_job_definition.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, @@ -578,16 +580,28 @@ func TestAccBatchJobDefinition_createTypeContainerWithBothProperties(t *testing. CheckDestroy: testAccCheckJobDefinitionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccJobDefinitionConfig_createTypeContainerWithBothProperties(rName), - ExpectError: regexache.MustCompile("No `node_properties` can be specified when `type` is \"container\""), + Config: testAccJobDefinitionConfig_EKSProperties_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckJobDefinitionExists(ctx, resourceName, &jd), + resource.TestCheckResourceAttr(resourceName, "eks_properties.0.pod_properties.0.containers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "eks_properties.0.pod_properties.0.containers.0.image_pull_policy", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "type", "container"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } - -func TestAccBatchJobDefinition_createTypeContainerWithNodeProperties(t *testing.T) { +func TestAccBatchJobDefinition_EKSProperties_update(t *testing.T) { ctx := acctest.Context(t) + var jd batch.JobDefinition rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_batch_job_definition.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, @@ -596,14 +610,32 @@ func TestAccBatchJobDefinition_createTypeContainerWithNodeProperties(t *testing. CheckDestroy: testAccCheckJobDefinitionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccJobDefinitionConfig_createTypeContainerWithNodeProperties(rName), - ExpectError: regexache.MustCompile("No `node_properties` can be specified when `type` is \"container\""), + Config: testAccJobDefinitionConfig_EKSProperties_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckJobDefinitionExists(ctx, resourceName, &jd), + ), + }, + { + Config: testAccJobDefinitionConfig_EKSProperties_advancedUpdate(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckJobDefinitionExists(ctx, resourceName, &jd), + resource.TestCheckResourceAttr(resourceName, "eks_properties.0.pod_properties.0.containers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "eks_properties.0.pod_properties.0.containers.0.image_pull_policy", "Always"), + resource.TestCheckResourceAttr(resourceName, "eks_properties.0.pod_properties.0.volumes.0.name", "tmp"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "type", "container"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } -func TestAccBatchJobDefinition_createTypeMultiNodeWithBothProperties(t *testing.T) { +func TestAccBatchJobDefinition_createTypeContainerWithNodeProperties(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -614,8 +646,8 @@ func TestAccBatchJobDefinition_createTypeMultiNodeWithBothProperties(t *testing. CheckDestroy: testAccCheckJobDefinitionDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccJobDefinitionConfig_createTypeMultiNodeWithBothProperties(rName), - ExpectError: regexache.MustCompile("No `container_properties` can be specified when `type` is \"multinode\""), + Config: testAccJobDefinitionConfig_createTypeContainerWithNodeProperties(rName), + ExpectError: regexache.MustCompile("No `node_properties` can be specified when `type` is \"container\""), }, }, }) @@ -1205,116 +1237,116 @@ resource "aws_batch_job_definition" "test" { `, rName) } -func testAccJobDefinitionConfig_createTypeContainerWithBothProperties(rName string) string { +func testAccJobDefinitionConfig_EKSProperties_basic(rName string) string { return fmt.Sprintf(` - - resource "aws_batch_job_definition" "test" { name = %[1]q type = "container" - parameters = { - param1 = "val1" - param2 = "val2" - } - timeout { - attempt_duration_seconds = 60 - } - - container_properties = jsonencode({ - command = ["echo", "test"] - image = "busybox" - memory = 128 - vcpus = 1 - }) - - node_properties = jsonencode({ - mainNode = 1 - nodeRangeProperties = [ - { - container = { - "command" : ["ls", "-la"], - "image" : "busybox", - "memory" : 512, - "vcpus" : 1 + eks_properties { + pod_properties { + host_network = true + containers { + image = "public.ecr.aws/amazonlinux/amazonlinux:1" + command = [ + "sleep", + "60" + ] + resources { + limits = { + cpu = "1" + memory = "1024Mi" + } } - targetNodes = "0:" - }, - { - container = { - command = ["echo", "test"] - environment = [] - image = "busybox" - memory = 128 - mountPoints = [] - ulimits = [] - vcpus = 1 - volumes = [] + } + metadata { + labels = { + environment = "test" + name = %[1]q } - targetNodes = "1:" } - ] - numNodes = 4 - }) - -} - `, rName) + } + } +}`, rName) } -func testAccJobDefinitionConfig_createTypeContainerWithNodeProperties(rName string) string { +func testAccJobDefinitionConfig_EKSProperties_advancedUpdate(rName string) string { return fmt.Sprintf(` - - resource "aws_batch_job_definition" "test" { name = %[1]q type = "container" + eks_properties { + pod_properties { + host_network = true + containers { + args = ["60"] + image = "public.ecr.aws/amazonlinux/amazonlinux:2" + image_pull_policy = "Always" + name = "sleep" + command = [ + "sleep", + ] + resources { + requests = { + cpu = "1" + memory = "1024Mi" + } + limits = { + cpu = "1" + memory = "1024Mi" + } + } + security_context { + privileged = true + read_only_root_file_system = true + run_as_group = 1000 + run_as_user = 1000 + run_as_non_root = true + } + volume_mounts { + mount_path = "/tmp" + read_only = true + name = "tmp" + } + env { + name = "Test" + value = "Environment Variable" + } + } + metadata { + labels = { + environment = "test" + name = %[1]q + } + } + volumes { + name = "tmp" + empty_dir { + medium = "Memory" + size_limit = "128Mi" + } + } + service_account_name = "test-service-account" + dns_policy = "ClusterFirst" + } + } parameters = { param1 = "val1" param2 = "val2" } + timeout { attempt_duration_seconds = 60 } - - node_properties = jsonencode({ - mainNode = 1 - nodeRangeProperties = [ - { - container = { - "command" : ["ls", "-la"], - "image" : "busybox", - "memory" : 512, - "vcpus" : 1 - } - targetNodes = "0:" - }, - { - container = { - command = ["echo", "test"] - environment = [] - image = "busybox" - memory = 128 - mountPoints = [] - ulimits = [] - vcpus = 1 - volumes = [] - } - targetNodes = "1:" - } - ] - numNodes = 4 - }) - -} - `, rName) +}`, rName) } -func testAccJobDefinitionConfig_createTypeMultiNodeWithBothProperties(rName string) string { +func testAccJobDefinitionConfig_createTypeContainerWithNodeProperties(rName string) string { return fmt.Sprintf(` resource "aws_batch_job_definition" "test" { name = %[1]q - type = "multinode" + type = "container" parameters = { param1 = "val1" param2 = "val2" @@ -1323,13 +1355,6 @@ resource "aws_batch_job_definition" "test" { attempt_duration_seconds = 60 } - container_properties = jsonencode({ - command = ["echo", "test"] - image = "busybox" - memory = 128 - vcpus = 1 - }) - node_properties = jsonencode({ mainNode = 1 nodeRangeProperties = [ diff --git a/website/docs/cdktf/python/r/batch_job_definition.html.markdown b/website/docs/cdktf/python/r/batch_job_definition.html.markdown index 0cc17c610e1..563c013da0c 100644 --- a/website/docs/cdktf/python/r/batch_job_definition.html.markdown +++ b/website/docs/cdktf/python/r/batch_job_definition.html.markdown @@ -239,4 +239,4 @@ Using `terraform import`, import Batch Job Definition using the `arn`. For examp % terraform import aws_batch_job_definition.test arn:aws:batch:us-east-1:123456789012:job-definition/sample ``` - \ No newline at end of file + diff --git a/website/docs/cdktf/typescript/r/batch_job_definition.html.markdown b/website/docs/cdktf/typescript/r/batch_job_definition.html.markdown index 9aabae28994..5ff17a21c13 100644 --- a/website/docs/cdktf/typescript/r/batch_job_definition.html.markdown +++ b/website/docs/cdktf/typescript/r/batch_job_definition.html.markdown @@ -271,4 +271,4 @@ Using `terraform import`, import Batch Job Definition using the `arn`. For examp % terraform import aws_batch_job_definition.test arn:aws:batch:us-east-1:123456789012:job-definition/sample ``` - \ No newline at end of file + diff --git a/website/docs/r/batch_job_definition.html.markdown b/website/docs/r/batch_job_definition.html.markdown index 4331cf6d38e..512adbf6646 100644 --- a/website/docs/r/batch_job_definition.html.markdown +++ b/website/docs/r/batch_job_definition.html.markdown @@ -102,6 +102,38 @@ resource "aws_batch_job_definition" "test" { } ``` +### Job Definitionn of type EKS + +```terraform +resource "aws_batch_job_definition" "test" { + name = " tf_test_batch_job_definition_eks" + type = "container" + eks_properties { + pod_properties { + host_network = true + containers { + image = "public.ecr.aws/amazonlinux/amazonlinux:1" + command = [ + "sleep", + "60" + ] + resources { + limits = { + cpu = "1" + memory = "1024Mi" + } + } + } + metadata { + labels = { + environment = "test" + } + } + } + } +} +``` + ### Fargate Platform Capability ```terraform @@ -169,9 +201,10 @@ The following arguments are required: The following arguments are optional: * `container_properties` - (Optional) A valid [container properties](http://docs.aws.amazon.com/batch/latest/APIReference/API_RegisterJobDefinition.html) - provided as a single valid JSON document. This parameter is required if the `type` parameter is `container`. + provided as a single valid JSON document. This parameter is only valid if the `type` parameter is `container`. * `node_properties` - (Optional) A valid [node properties](http://docs.aws.amazon.com/batch/latest/APIReference/API_RegisterJobDefinition.html) provided as a single valid JSON document. This parameter is required if the `type` parameter is `multinode`. +* `eks_properties` - (Optional) A valid [eks properties](#eks_properties). This parameter is only valid if the `type` parameter is `container`. * `parameters` - (Optional) Specifies the parameter substitution placeholders to set in the job definition. * `platform_capabilities` - (Optional) The platform capabilities required by the job definition. If no value is specified, it defaults to `EC2`. To run the job on Fargate resources, specify `FARGATE`. * `propagate_tags` - (Optional) Specifies whether to propagate the tags from the job definition to the corresponding Amazon ECS task. Default is `false`. @@ -180,19 +213,63 @@ The following arguments are optional: * `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `timeout` - (Optional) Specifies the timeout for jobs so that if a job runs longer, AWS Batch terminates the job. Maximum number of `timeout` is `1`. Defined below. -### retry_strategy +### `eks_properties` + +* `pod_properties` - The properties for the Kubernetes pod resources of a job. See [`pod_properties`](#pod_properties) below. + +### `pod_properties` + +* `containers` - The properties of the container that's used on the Amazon EKS pod. See [containers](#containers) below. +* `dns_policy` - (Optional) The DNS policy for the pod. The default value is `ClusterFirst`. If the `host_network` argument is not specified, the default is `ClusterFirstWithHostNet`. `ClusterFirst` indicates that any DNS query that does not match the configured cluster domain suffix is forwarded to the upstream nameserver inherited from the node. For more information, see Pod's DNS policy in the Kubernetes documentation. +* `host_network` - (Optional) Indicates if the pod uses the hosts' network IP address. The default value is `true`. Setting this to `false` enables the Kubernetes pod networking model. Most AWS Batch workloads are egress-only and don't require the overhead of IP allocation for each pod for incoming connections. +* `metadata` - (Optional) Metadata about the Kubernetes pod. +* `service_account_name` - (Optional) The name of the service account that's used to run the pod. +* `volumes` - (Optional) Specifies the volumes for a job definition that uses Amazon EKS resources. AWS Batch supports [emptyDir](#eks_empty_dir), [hostPath](#eks_host_path), and [secret](#eks_secret) volume types. + +### `containers` + +* `image` - The Docker image used to start the container. +* `args` - An array of arguments to the entrypoint. If this isn't specified, the CMD of the container image is used. This corresponds to the args member in the Entrypoint portion of the Pod in Kubernetes. Environment variable references are expanded using the container's environment. +* `command` - The entrypoint for the container. This isn't run within a shell. If this isn't specified, the ENTRYPOINT of the container image is used. Environment variable references are expanded using the container's environment. +* `env` - The environment variables to pass to a container. See [EKS Environment](#eks_environment) below. +* `image_pull_policy` - The image pull policy for the container. Supported values are `Always`, `IfNotPresent`, and `Never`. +* `name` - The name of the container. If the name isn't specified, the default name "Default" is used. Each container in a pod must have a unique name. +* `resources` - The type and amount of resources to assign to a container. The supported resources include `memory`, `cpu`, and `nvidia.com/gpu`. +* `security_context` - The security context for a job. +* `volume_mounts` - The volume mounts for the container. + +### `eks_environment` + +* `name` - The name of the environment variable. +* `value` - The value of the environment variable. + +### `eks_empty_dir` + +* `medium` - (Optional) The medium to store the volume. The default value is an empty string, which uses the storage of the node. +* `size_limit` - The maximum size of the volume. By default, there's no maximum size defined. + +### `eks_host_path` + +* `path` - The path of the file or directory on the host to mount into containers on the pod. + +### `eks_secret` + +* `secret_name` - The name of the secret. The name must be allowed as a DNS subdomain name. +* `optional` - (Optional) Specifies whether the secret or the secret's keys must be defined. + +### `retry_strategy` * `attempts` - (Optional) The number of times to move a job to the `RUNNABLE` status. You may specify between `1` and `10` attempts. * `evaluate_on_exit` - (Optional) The [evaluate on exit](#evaluate_on_exit) conditions under which the job should be retried or failed. If this parameter is specified, then the `attempts` parameter must also be specified. You may specify up to 5 configuration blocks. -#### evaluate_on_exit +#### `evaluate_on_exit` * `action` - (Required) Specifies the action to take if all of the specified conditions are met. The values are not case sensitive. Valid values: `RETRY`, `EXIT`. * `on_exit_code` - (Optional) A glob pattern to match against the decimal representation of the exit code returned for a job. * `on_reason` - (Optional) A glob pattern to match against the reason returned for a job. * `on_status_reason` - (Optional) A glob pattern to match against the status reason returned for a job. -### timeout +### `timeout` * `attempt_duration_seconds` - (Optional) The time duration in seconds after which AWS Batch terminates your jobs if they have not finished. The minimum value for the timeout is `60` seconds.