diff --git a/.changelog/37889.txt b/.changelog/37889.txt new file mode 100644 index 000000000000..190c669a721c --- /dev/null +++ b/.changelog/37889.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_emrserverless_application: Add `interactive_configuration` argument +``` \ No newline at end of file diff --git a/internal/service/emrserverless/application.go b/internal/service/emrserverless/application.go index 0c2b1b2cb9ba..996af4ed4f7e 100644 --- a/internal/service/emrserverless/application.go +++ b/internal/service/emrserverless/application.go @@ -155,6 +155,26 @@ func resourceApplication() *schema.Resource { }, }, }, + "interactive_configuration": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "livy_endpoint_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "studio_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, "maximum_capacity": { Type: schema.TypeList, Optional: true, @@ -258,6 +278,10 @@ func resourceApplicationCreate(ctx context.Context, d *schema.ResourceData, meta input.InitialCapacity = expandInitialCapacity(v.(*schema.Set)) } + if v, ok := d.GetOk("interactive_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.InteractiveConfiguration = expandInteractiveConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + if v, ok := d.GetOk("maximum_capacity"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.MaximumCapacity = expandMaximumCapacity(v.([]interface{})[0].(map[string]interface{})) } @@ -319,6 +343,10 @@ func resourceApplicationRead(ctx context.Context, d *schema.ResourceData, meta i return sdkdiag.AppendErrorf(diags, "setting initial_capacity: %s", err) } + if err := d.Set("interactive_configuration", []interface{}{flattenInteractiveConfiguration(application.InteractiveConfiguration)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting interactive_configuration: %s", err) + } + if err := d.Set("maximum_capacity", []interface{}{flattenMaximumCapacity(application.MaximumCapacity)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting maximum_capacity: %s", err) } @@ -362,6 +390,10 @@ func resourceApplicationUpdate(ctx context.Context, d *schema.ResourceData, meta input.InitialCapacity = expandInitialCapacity(v.(*schema.Set)) } + if v, ok := d.GetOk("interactive_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.InteractiveConfiguration = expandInteractiveConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + if v, ok := d.GetOk("maximum_capacity"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.MaximumCapacity = expandMaximumCapacity(v.([]interface{})[0].(map[string]interface{})) } @@ -573,6 +605,42 @@ func flattenAutoStopConfig(apiObject *types.AutoStopConfig) map[string]interface return tfMap } +func expandInteractiveConfiguration(tfMap map[string]interface{}) *types.InteractiveConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &types.InteractiveConfiguration{} + + if v, ok := tfMap["livy_endpoint_enabled"].(bool); ok { + apiObject.LivyEndpointEnabled = aws.Bool(v) + } + + if v, ok := tfMap["studio_enabled"].(bool); ok { + apiObject.StudioEnabled = aws.Bool(v) + } + + return apiObject +} + +func flattenInteractiveConfiguration(apiObject *types.InteractiveConfiguration) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.LivyEndpointEnabled; v != nil { + tfMap["livy_endpoint_enabled"] = aws.ToBool(v) + } + + if v := apiObject.StudioEnabled; v != nil { + tfMap["studio_enabled"] = aws.ToBool(v) + } + + return tfMap +} + func expandMaximumCapacity(tfMap map[string]interface{}) *types.MaximumAllowedResources { if tfMap == nil { return nil diff --git a/internal/service/emrserverless/application_test.go b/internal/service/emrserverless/application_test.go index 58499d1f5d20..a0317904a3c8 100644 --- a/internal/service/emrserverless/application_test.go +++ b/internal/service/emrserverless/application_test.go @@ -230,6 +230,63 @@ func TestAccEMRServerlessApplication_imageConfiguration(t *testing.T) { }) } +func TestAccEMRServerlessApplication_interactiveConfiguration(t *testing.T) { + ctx := acctest.Context(t) + var application types.Application + resourceName := "aws_emrserverless_application.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EMRServerlessServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckApplicationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccApplicationConfig_interactiveConfiguration(rName, true, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.0.livy_endpoint_enabled", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.0.studio_enabled", acctest.CtTrue), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccApplicationConfig_interactiveConfiguration(rName, true, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.0.livy_endpoint_enabled", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.0.studio_enabled", acctest.CtFalse), + ), + }, + { + Config: testAccApplicationConfig_interactiveConfiguration(rName, false, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.0.livy_endpoint_enabled", acctest.CtFalse), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.0.studio_enabled", acctest.CtTrue), + ), + }, + { + Config: testAccApplicationConfig_interactiveConfiguration(rName, false, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "interactive_configuration.#", acctest.Ct1), + resource.TestCheckNoResourceAttr(resourceName, "interactive_configuration.0.livy_endpoint_enabled"), + resource.TestCheckNoResourceAttr(resourceName, "interactive_configuration.0.studio_enabled"), + ), + }, + }, + }) +} + func TestAccEMRServerlessApplication_maxCapacity(t *testing.T) { ctx := acctest.Context(t) var application types.Application @@ -460,6 +517,20 @@ resource "aws_emrserverless_application" "test" { `, rName, cpu) } +func testAccApplicationConfig_interactiveConfiguration(rName string, livyEndpointEnabled, studioEnabled bool) string { + return fmt.Sprintf(` +resource "aws_emrserverless_application" "test" { + name = %[1]q + release_label = "emr-7.1.0" + type = "spark" + interactive_configuration { + livy_endpoint_enabled = %[2]t + studio_enabled = %[3]t + } +} +`, rName, livyEndpointEnabled, studioEnabled) +} + func testAccApplicationConfig_maxCapacity(rName, cpu string) string { return fmt.Sprintf(` resource "aws_emrserverless_application" "test" { diff --git a/website/docs/r/emrserverless_application.html.markdown b/website/docs/r/emrserverless_application.html.markdown index d6d7dc907448..aebec5bac01a 100644 --- a/website/docs/r/emrserverless_application.html.markdown +++ b/website/docs/r/emrserverless_application.html.markdown @@ -68,6 +68,7 @@ The following arguments are required: * `auto_stop_configuration` – (Optional) The configuration for an application to automatically stop after a certain amount of time being idle. * `image_configuration` – (Optional) The image configuration applied to all worker types. * `initial_capacity` – (Optional) The capacity to initialize when the application is created. +* `interactive_configuration` – (Optional) Enables the interactive use cases to use when running an application. * `maximum_capacity` – (Optional) The maximum capacity to allocate when the application is created. This is cumulative across all workers at any given point in time, not just when an application is created. No new resources will be created once any one of the defined limits is hit. * `name` – (Required) The name of the application. * `network_configuration` – (Optional) The network configuration for customer VPC connectivity. @@ -109,6 +110,11 @@ The following arguments are required: * `worker_configuration` - (Optional) The resource configuration of the initial capacity configuration. * `worker_count` - (Required) The number of workers in the initial capacity configuration. +### interactive_configuration Arguments + +* `livy_endpoint_enabled` - (Optional) Enables an Apache Livy endpoint that you can connect to and run interactive jobs. +* `studio_enabled` - (Optional) Enables you to connect an application to Amazon EMR Studio to run interactive workloads in a notebook. + ##### worker_configuration Arguments * `cpu` - (Required) The CPU requirements for every worker instance of the worker type.