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

Added multi-session support on the appstream_fleet resource #34266

Merged
7 changes: 7 additions & 0 deletions .changelog/34266.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_appstream_fleet: Add `desired_sessions` argument to the `compute_capacity` block.
```

```release-note:enhancement
resource/aws_appstream_fleet: Add `max_sessions_per_instance` argument.
```
41 changes: 38 additions & 3 deletions internal/service/appstream/fleet.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,17 @@ func ResourceFleet() *schema.Resource {
},
"desired_instances": {
Type: schema.TypeInt,
Required: true,
Optional: true,
ExactlyOneOf: []string{
"compute_capacity.0.desired_sessions",
},
},
"desired_sessions": {
Type: schema.TypeInt,
Optional: true,
ExactlyOneOf: []string{
"compute_capacity.0.desired_instances",
},
},
"in_use": {
Type: schema.TypeInt,
Expand Down Expand Up @@ -155,6 +165,10 @@ func ResourceFleet() *schema.Resource {
Type: schema.TypeString,
Required: true,
},
"max_sessions_per_instance": {
Type: schema.TypeInt,
Optional: true,
},
"max_user_duration_in_seconds": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -255,6 +269,10 @@ func resourceFleetCreate(ctx context.Context, d *schema.ResourceData, meta inter
input.IamRoleArn = aws.String(v.(string))
}

if v, ok := d.GetOk("max_sessions_per_instance"); ok {
input.MaxSessionsPerInstance = aws.Int64(int64(v.(int)))
}

if v, ok := d.GetOk("max_user_duration_in_seconds"); ok {
input.MaxUserDurationInSeconds = aws.Int64(int64(v.(int)))
}
Expand Down Expand Up @@ -372,6 +390,7 @@ func resourceFleetRead(ctx context.Context, d *schema.ResourceData, meta interfa
d.Set("image_name", fleet.ImageName)
d.Set("image_arn", fleet.ImageArn)
d.Set("instance_type", fleet.InstanceType)
d.Set("max_sessions_per_instance", fleet.MaxSessionsPerInstance)
d.Set("max_user_duration_in_seconds", fleet.MaxUserDurationInSeconds)
d.Set("name", fleet.Name)
d.Set("state", fleet.State)
Expand Down Expand Up @@ -462,6 +481,10 @@ func resourceFleetUpdate(ctx context.Context, d *schema.ResourceData, meta inter
input.InstanceType = aws.String(d.Get("instance_type").(string))
}

if d.HasChange("max_sessions_per_instance") {
input.MaxSessionsPerInstance = aws.Int64(int64(d.Get("max_sessions_per_instance").(int)))
}

if d.HasChange("max_user_duration_in_seconds") {
input.MaxUserDurationInSeconds = aws.Int64(int64(d.Get("max_user_duration_in_seconds").(int)))
}
Expand Down Expand Up @@ -502,6 +525,9 @@ func resourceFleetDelete(ctx context.Context, d *schema.ResourceData, meta inter
_, err := conn.StopFleetWithContext(ctx, &appstream.StopFleetInput{
Name: aws.String(d.Id()),
})
if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) {
return diags
}
if err != nil {
return sdkdiag.AppendErrorf(diags, "stopping Appstream Fleet (%s): %s", d.Id(), err)
}
Expand Down Expand Up @@ -545,10 +571,14 @@ func expandComputeCapacity(tfList []interface{}) *appstream.ComputeCapacity {
apiObject := &appstream.ComputeCapacity{}

attr := tfList[0].(map[string]interface{})
if v, ok := attr["desired_instances"]; ok {
if v, ok := attr["desired_instances"]; ok && v != 0 {
apiObject.DesiredInstances = aws.Int64(int64(v.(int)))
}

if v, ok := attr["desired_sessions"]; ok && v != 0 {
apiObject.DesiredSessions = aws.Int64(int64(v.(int)))
}

if reflect.DeepEqual(&appstream.ComputeCapacity{}, apiObject) {
return nil
}
Expand All @@ -563,7 +593,12 @@ func flattenComputeCapacity(apiObject *appstream.ComputeCapacityStatus) map[stri

tfMap := map[string]interface{}{}

if v := apiObject.Desired; v != nil {
if v := apiObject.DesiredUserSessions; v != nil {
tfMap["desired_sessions"] = aws.Int64Value(v)
}

// desiredInstances is always returned by the API but cannot be used in conjunction with desiredSessions
if v := apiObject.Desired; v != nil && tfMap["desired_sessions"] == nil {
tfMap["desired_instances"] = aws.Int64Value(v)
}

Expand Down
91 changes: 91 additions & 0 deletions internal/service/appstream/fleet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,55 @@ func TestAccAppStreamFleet_emptyDomainJoin(t *testing.T) {
})
}

func TestAccAppStreamFleet_multiSession(t *testing.T) {
ctx := acctest.Context(t)
var fleetOutput appstream.Fleet
resourceName := "aws_appstream_fleet.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
instanceType := "stream.standard.small"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
acctest.PreCheckHasIAMRole(ctx, t, "AmazonAppStreamServiceAccess")
},
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckFleetDestroy(ctx),
ErrorCheck: acctest.ErrorCheck(t, names.AppStreamServiceID),
Steps: []resource.TestStep{
{
Config: testAccFleetConfig_multiSession(rName, instanceType, 1, 5),
Check: resource.ComposeTestCheckFunc(
testAccCheckFleetExists(ctx, resourceName, &fleetOutput),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "instance_type", instanceType),
resource.TestCheckResourceAttr(resourceName, "max_sessions_per_instance", "5"),
resource.TestCheckResourceAttr(resourceName, "compute_capacity.0.desired_sessions", "1"),
resource.TestCheckResourceAttr(resourceName, "state", appstream.FleetStateRunning),
acctest.CheckResourceAttrRFC3339(resourceName, "created_time"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccFleetConfig_multiSession(rName, instanceType, 2, 10),
Check: resource.ComposeTestCheckFunc(
testAccCheckFleetExists(ctx, resourceName, &fleetOutput),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "instance_type", instanceType),
resource.TestCheckResourceAttr(resourceName, "max_sessions_per_instance", "10"),
resource.TestCheckResourceAttr(resourceName, "compute_capacity.0.desired_sessions", "2"),
resource.TestCheckResourceAttr(resourceName, "state", appstream.FleetStateRunning),
acctest.CheckResourceAttrRFC3339(resourceName, "created_time"),
),
},
},
})
}

func testAccCheckFleetExists(ctx context.Context, resourceName string, appStreamFleet *appstream.Fleet) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
Expand Down Expand Up @@ -516,3 +565,45 @@ resource "aws_appstream_fleet" "test" {
}
`, name, instanceType, empty)
}

func testAccFleetConfig_multiSession(name, instanceType string, desiredSessions, maxSessionsPerInstance int) string {
return acctest.ConfigCompose(
acctest.ConfigAvailableAZsNoOptIn(),
fmt.Sprintf(`
data "aws_region" "current" {}
data "aws_partition" "current" {}

resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "test" {
count = 2
availability_zone = data.aws_availability_zones.available.names[count.index]
cidr_block = "10.0.${count.index}.0/24"
vpc_id = aws_vpc.test.id
}

resource "aws_appstream_fleet" "test" {
name = %[1]q
image_arn = "arn:${data.aws_partition.current.partition}:appstream:${data.aws_region.current.name}::image/AppStream-WinServer2019-01-26-2024"

compute_capacity {
desired_sessions = %[3]d
}

description = "Description for a multi-session fleet"
idle_disconnect_timeout_in_seconds = 70
enable_default_internet_access = false
fleet_type = "ON_DEMAND"
instance_type = %[2]q
max_sessions_per_instance = %[4]d
max_user_duration_in_seconds = 1000
stream_view = "DESKTOP"

vpc_config {
subnet_ids = aws_subnet.test[*].id
}
}
`, name, instanceType, desiredSessions, maxSessionsPerInstance))
}
6 changes: 5 additions & 1 deletion website/docs/r/appstream_fleet.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ The following arguments are optional:
* `image_name` - (Optional) Name of the image used to create the fleet.
* `image_arn` - (Optional) ARN of the public, private, or shared image to use.
* `stream_view` - (Optional) AppStream 2.0 view that is displayed to your users when they stream from the fleet. When `APP` is specified, only the windows of applications opened by users display. When `DESKTOP` is specified, the standard desktop that is provided by the operating system displays. If not specified, defaults to `APP`.
* `max_sessions_per_instance` - (Optional) The maximum number of user sessions on an instance. This only applies to multi-session fleets.
* `max_user_duration_in_seconds` - (Optional) Maximum amount of time that a streaming session can remain active, in seconds.
* `vpc_config` - (Optional) Configuration block for the VPC configuration for the image builder. See below.
* `tags` - (Optional) Map of tags to attach to AppStream instances.

### `compute_capacity`

* `desired_instances` - (Required) Desired number of streaming instances.
Exactly one of `desired_instances` or `desired_sessions` must be set, based on the type of fleet being created.

* `desired_instances` - (Optional) Desired number of streaming instances.
* `desired_sessions` - (Optional) Desired number of user sessions for a multi-session fleet. This is not allowed for single-session fleets.

### `domain_join_info`

Expand Down
Loading