From d82fbda22a5e080966de40c0cf78c723ff9e971e Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 5 Aug 2021 18:22:26 -0600 Subject: [PATCH 01/20] feat: added resource for appstream stack and tests docs --- aws/provider.go | 1 + aws/resource_aws_appstream_stack.go | 494 +++++++++++++++++++ aws/resource_aws_appstream_stack_test.go | 187 +++++++ aws/resource_aws_appstream_test.go | 27 + website/docs/r/appstream_stack.html.markdown | 70 +++ 5 files changed, 779 insertions(+) create mode 100644 aws/resource_aws_appstream_stack.go create mode 100644 aws/resource_aws_appstream_stack_test.go create mode 100644 aws/resource_aws_appstream_test.go create mode 100644 website/docs/r/appstream_stack.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 9d5e0fb041d..937cd7691e6 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -526,6 +526,7 @@ func Provider() *schema.Provider { "aws_apprunner_connection": resourceAwsAppRunnerConnection(), "aws_apprunner_custom_domain_association": resourceAwsAppRunnerCustomDomainAssociation(), "aws_apprunner_service": resourceAwsAppRunnerService(), + "aws_appstream_stack": resourceAwsAppstreamStack(), "aws_appsync_api_key": resourceAwsAppsyncApiKey(), "aws_appsync_datasource": resourceAwsAppsyncDatasource(), "aws_appsync_function": resourceAwsAppsyncFunction(), diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go new file mode 100644 index 00000000000..79e72791876 --- /dev/null +++ b/aws/resource_aws_appstream_stack.go @@ -0,0 +1,494 @@ +package aws + +import ( + "context" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appstream" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" +) + +func resourceAwsAppstreamStack() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceAwsAppstreamStackCreate, + ReadWithoutTimeout: resourceAwsAppstreamStackRead, + UpdateWithoutTimeout: resourceAwsAppstreamStackUpdate, + DeleteWithoutTimeout: resourceAwsAppstreamStackDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "access_endpoints": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + MinItems: 1, + MaxItems: 4, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "endpoint_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appstream.AccessEndpointType_Values(), false), + }, + "vpce_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + "application_settings": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "settings_group": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(0, 100), + }, + "embed_host_domains": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MinItems: 1, + MaxItems: 20, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 128), + }, + }, + "feedback_url": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringLenBetween(0, 100), + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name_prefix"}, + }, + "name_prefix": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name"}, + }, + "redirect_url": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringLenBetween(0, 100), + }, + "storage_connectors": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "connector_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appstream.StorageConnectorType_Values(), false), + }, + "domains": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 50, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(1, 64), + }, + }, + "resource_identifier": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 2048), + }, + }, + }, + }, + "user_settings": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appstream.Action_Values(), false), + }, + "permission": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appstream.Permission_Values(), false), + }, + }, + }, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchemaForceNew(), + "tags_all": tagsSchemaComputed(), + }, + } +} + +func resourceAwsAppstreamStackCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).appstreamconn + input := &appstream.CreateStackInput{ + Name: aws.String(naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string))), + } + + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + if v, ok := d.GetOk("access_endpoints"); ok { + input.AccessEndpoints = expandAccessEndpoints(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("application_settings"); ok { + input.ApplicationSettings = expandApplicationSettings(v.([]interface{})) + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("display_name"); ok { + input.DisplayName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("embed_host_domains"); ok { + input.EmbedHostDomains = expandStringList(v.([]interface{})) + } + + if v, ok := d.GetOk("feedback_url"); ok { + input.FeedbackURL = aws.String(v.(string)) + } + + if v, ok := d.GetOk("redirect_url"); ok { + input.RedirectURL = aws.String(v.(string)) + } + + if v, ok := d.GetOk("storage_connectors"); ok { + input.StorageConnectors = expandStorageConnectors(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("user_settings"); ok { + input.UserSettings = expandUserSettings(v.(*schema.Set).List()) + } + + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().AppstreamTags() + } + + resp, err := conn.CreateStackWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Appstream Stack (%s): %w", d.Id(), err)) + } + + d.SetId(aws.StringValue(resp.Stack.Name)) + + return resourceAwsAppstreamStackRead(ctx, d, meta) +} + +func resourceAwsAppstreamStackRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).appstreamconn + + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + resp, err := conn.DescribeStacksWithContext(ctx, &appstream.DescribeStacksInput{Names: []*string{aws.String(d.Id())}}) + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading Appstream Stack (%s): %w", d.Id(), err)) + } + for _, v := range resp.Stacks { + d.Set("name", v.Name) + d.Set("name_prefix", naming.NamePrefixFromName(aws.StringValue(v.Name))) + + if err = d.Set("access_endpoints", flattenAccessEndpoints(v.AccessEndpoints)); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "access_endpoints", d.Id(), err)) + } + if err = d.Set("storage_connectors", flattenStorageConnectors(v.StorageConnectors)); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "storage_connectors", d.Id(), err)) + } + if err = d.Set("user_settings", flattenUserSettings(v.UserSettings)); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) + } + + d.Set("name", v.Name) + d.Set("description", v.Description) + d.Set("display_name", v.DisplayName) + d.Set("feedback_url", v.FeedbackURL) + d.Set("redirect_url", v.RedirectURL) + d.Set("arn", v.Arn) + + tg, err := conn.ListTagsForResource(&appstream.ListTagsForResourceInput{ + ResourceArn: v.Arn, + }) + if err != nil { + return diag.FromErr(fmt.Errorf("error listing stack tags for AppStream Stack (%s): %w", d.Id(), err)) + } + if tg.Tags == nil { + log.Printf("[DEBUG] Apsstream Stack tags (%s) not found", d.Id()) + return nil + } + tags := keyvaluetags.AppstreamKeyValueTags(tg.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + if err = d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "tags", d.Id(), err)) + } + + if err = d.Set("tags_all", tags.Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "tags_all", d.Id(), err)) + } + return nil + } + return nil +} + +func resourceAwsAppstreamStackUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + + svc := meta.(*AWSClient).appstreamconn + + UpdateStackInputOpts := &appstream.UpdateStackInput{ + Name: aws.String(naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string))), + } + + if d.HasChange("description") { + UpdateStackInputOpts.Description = aws.String(d.Get("description").(string)) + } + + if d.HasChange("display_name") { + UpdateStackInputOpts.DisplayName = aws.String(d.Get("display_name").(string)) + } + + if d.HasChange("feedback_url") { + UpdateStackInputOpts.FeedbackURL = aws.String(d.Get("feedback_url").(string)) + } + + if d.HasChange("redirect_url") { + UpdateStackInputOpts.RedirectURL = aws.String(d.Get("redirect_url").(string)) + } + + if d.HasChange("user_settings") { + UpdateStackInputOpts.UserSettings = expandUserSettings(d.Get("user_settings").(*schema.Set).List()) + } + + if d.HasChange("application_settings") { + UpdateStackInputOpts.ApplicationSettings = expandApplicationSettings(d.Get("application_settings").(*schema.Set).List()) + } + + _, err := svc.UpdateStack(UpdateStackInputOpts) + + if err != nil { + diag.FromErr(fmt.Errorf("error updating Appstream Stack (%s): %w", d.Id(), err)) + } + + return resourceAwsAppstreamStackRead(ctx, d, meta) +} + +func resourceAwsAppstreamStackDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).appstreamconn + + _, err := conn.DeleteStackWithContext(ctx, &appstream.DeleteStackInput{ + Name: aws.String(d.Id()), + }) + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting Appstream Stack (%s): %w", d.Id(), err)) + } + return nil +} + +func expandAccessEndpoints(accessEndpoints []interface{}) []*appstream.AccessEndpoint { + if len(accessEndpoints) == 0 { + return nil + } + + var endpoints []*appstream.AccessEndpoint + + for _, v := range accessEndpoints { + v1 := v.(map[string]interface{}) + + endpoint := &appstream.AccessEndpoint{ + EndpointType: aws.String(v1["endpoint_type"].(string)), + } + if v2, ok := v1["vpce_id"]; ok { + endpoint.VpceId = aws.String(v2.(string)) + } + + endpoints = append(endpoints, endpoint) + } + + return endpoints +} + +func flattenAccessEndpoints(accessEndpoints []*appstream.AccessEndpoint) []map[string]interface{} { + if accessEndpoints == nil { + return nil + } + + var endpoints []map[string]interface{} + + for _, endpoint := range accessEndpoints { + endpoints = append(endpoints, map[string]interface{}{ + "endpoint_type": aws.StringValue(endpoint.EndpointType), + "vpce_id": aws.StringValue(endpoint.VpceId), + }) + } + + return endpoints +} + +func expandApplicationSettings(applicationSettings []interface{}) *appstream.ApplicationSettings { + if len(applicationSettings) == 0 { + return nil + } + + applicationSetting := &appstream.ApplicationSettings{} + + attr := applicationSettings[0].(map[string]interface{}) + if v, ok := attr["enabled"]; ok { + applicationSetting.Enabled = aws.Bool(v.(bool)) + } + if v, ok := attr["settings_group"]; ok { + applicationSetting.SettingsGroup = aws.String(v.(string)) + } + + return applicationSetting +} + +func flattenApplicationSettings(applicationSettings *appstream.ApplicationSettings) []interface{} { + if applicationSettings == nil { + return nil + } + + applicationSetting := map[string]interface{}{} + applicationSetting["enabled"] = aws.BoolValue(applicationSettings.Enabled) + applicationSetting["SettingsGroup"] = aws.StringValue(applicationSettings.SettingsGroup) + + return []interface{}{applicationSetting} +} + +func expandStorageConnectors(storageConnectors []interface{}) []*appstream.StorageConnector { + if len(storageConnectors) == 0 { + return nil + } + + var connectors []*appstream.StorageConnector + + for _, v := range storageConnectors { + v1 := v.(map[string]interface{}) + + connector := &appstream.StorageConnector{ + ConnectorType: aws.String(v1["connector_type"].(string)), + } + if v2, ok := v1["domains"]; ok { + connector.Domains = expandStringList(v2.([]interface{})) + } + if v2, ok := v1["resource_identifier"]; ok { + connector.ResourceIdentifier = aws.String(v2.(string)) + } + + connectors = append(connectors, connector) + } + + return connectors +} + +func flattenStorageConnectors(storageConnectors []*appstream.StorageConnector) []map[string]interface{} { + if storageConnectors == nil { + return nil + } + + var connectors []map[string]interface{} + + for _, connector := range storageConnectors { + connectors = append(connectors, map[string]interface{}{ + "connector_type": aws.StringValue(connector.ConnectorType), + "domains": aws.StringValueSlice(connector.Domains), + "resource_identifier": aws.StringValue(connector.ResourceIdentifier), + }) + } + + return connectors +} + +func expandUserSettings(userSettings []interface{}) []*appstream.UserSetting { + if len(userSettings) == 0 { + return nil + } + + var users []*appstream.UserSetting + + for _, v := range userSettings { + v1 := v.(map[string]interface{}) + + user := &appstream.UserSetting{ + Action: aws.String(v1["action"].(string)), + Permission: aws.String(v1["permission"].(string)), + } + + users = append(users, user) + } + + return users +} + +func flattenUserSettings(userSettings []*appstream.UserSetting) []map[string]interface{} { + if userSettings == nil { + return nil + } + + var users []map[string]interface{} + + for _, user := range userSettings { + users = append(users, map[string]interface{}{ + "action": aws.StringValue(user.Action), + "permission": aws.StringValue(user.Permission), + }) + } + + return users +} diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go new file mode 100644 index 00000000000..9ec496ceb1a --- /dev/null +++ b/aws/resource_aws_appstream_stack_test.go @@ -0,0 +1,187 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appstream" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccAwsAppStreamStack_basic(t *testing.T) { + var stackOutput appstream.Stack + resourceName := "aws_appstream_stack.test" + stackName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsAppStreamStackDestroy, + ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsAppStreamStackConfigBasic(stackName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + ), + }, + { + Config: testAccAwsAppStreamStackConfigBasic(stackName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppStreamStack_disappears(t *testing.T) { + var stackOutput appstream.Stack + resourceName := "aws_appstream_stack.test" + stackName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsAppStreamStackDestroy, + ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsAppStreamStackConfigBasic(stackName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppstreamStack(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAwsAppStreamStack_withTags(t *testing.T) { + var stackOutput appstream.Stack + resourceName := "aws_appstream_stack.test" + stackName := acctest.RandomWithPrefix("tf-acc-test") + description := "Description of a fleet" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsAppStreamStackDestroy, + ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsAppStreamStackConfigWithTags(stackName, description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.Key", "value"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsAppStreamStackExists(resourceName string, appStreamStack *appstream.Stack) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).appstreamconn + resp, err := conn.DescribeStacks(&appstream.DescribeStacksInput{Names: []*string{aws.String(rs.Primary.ID)}}) + + if err != nil { + return err + } + + if resp == nil && len(resp.Stacks) == 0 { + return fmt.Errorf("appstream fleet %q does not exist", rs.Primary.ID) + } + + *appStreamStack = *resp.Stacks[0] + + return nil + } +} + +func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appstreamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appstream_stack" { + continue + } + + resp, err := conn.DescribeStacks(&appstream.DescribeStacksInput{Names: []*string{aws.String(rs.Primary.ID)}}) + + if err != nil { + return err + } + + if resp != nil && len(resp.Stacks) > 0 { + return fmt.Errorf("appstream fleet %q still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccAwsAppStreamStackConfigBasic(stackName string) string { + return fmt.Sprintf(` +resource "aws_appstream_stack" "test_fleet" { + name = %[1]q +} +`, stackName) +} + +func testAccAwsAppStreamStackConfigWithTags(stackName, description string) string { + return fmt.Sprintf(` +resource "aws_appstream_stack" "test" { + name = %[1]q + compute_capacity { + desired_instances = 1 + } + description = %[2]q + display_name = %[1]q + storage_connectors { + connector_type = "HOMEFOLDERS" + } + user_settings { + action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" + enabled = true + } + user_settings { + action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" + enabled = true + } + user_settings { + action = "FILE_UPLOAD" + enabled = true + } + user_settings { + action = "FILE_DOWNLOAD" + enabled = true + } + application_settings { + enabled = true + settings_group = "SettingsGroup" + } + tags = { + Key = "value" + } +} +`, stackName, description) +} diff --git a/aws/resource_aws_appstream_test.go b/aws/resource_aws_appstream_test.go new file mode 100644 index 00000000000..0e57e829c7c --- /dev/null +++ b/aws/resource_aws_appstream_test.go @@ -0,0 +1,27 @@ +package aws + +import ( + "testing" +) + +func TestAccAWSAppStreamResource_serial(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "Stack": { + "basic": testAccAwsAppStreamStack_basic, + "tags": testAccAwsAppStreamStack_withTags, + "disappears": testAccAwsAppStreamStack_disappears, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } + }) + } +} diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown new file mode 100644 index 00000000000..490be4803f5 --- /dev/null +++ b/website/docs/r/appstream_stack.html.markdown @@ -0,0 +1,70 @@ +--- +subcategory: "AppStream" +layout: "aws" +page_title: "AWS: aws_appstream_stack" +description: |- +Provides an AppStream stack +--- + +# Resource: aws_appstream_stack + +Provides an AppStream stack. + +## Example Usage + +```hcl +resource "aws_appstream_stack" "appstream_stack" { + name = "stack name" + description = "stack description" + display_name = "stack display name" + feedback_url = "http://your-domain/feedback" + redirect_url = "http://your-domain/redirect" + storage_connectors { + connector_type = "HOMEFOLDERS" + } + user_settings { + action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" + enabled = true + } + user_settings { + action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" + enabled = true + } + user_settings { + action = "FILE_UPLOAD" + enabled = true + } + user_settings { + action = "FILE_DOWNLOAD" + enabled = true + } + application_settings { + enabled = true + settings_group = "SettingsGroup" + } + + tags = { + TagName = "TagValue" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the AppStream stack, used as the stack's identifier. Only allows alphanumeric, hypen, underscore and period. +* `description` - (Optional) Description for the AppStream stack. +* `display_name` - (Optional) Human-readable friendly name for the AppStream stack. +* `redirect_url` - (Optional) URL to redirect at end of session. +* `feedback_url` - (Optional) URL for users to submit feedback. +* `storage_connectors` - (Optional) Nested block of storage connectors. + * `storage_connectors` - (Optional) Nested block of storage connectors. + * `storage_connectors` - (Optional) Nested block of storage connectors. +* `user_settings` - (Optional) Nested block of AppStream user settings. +* `application_settings` - (Optional) settings for application settings persistence. + +## Attributes Reference + +* `id` - The unique identifier (ID) of the appstream fleet. +* `arn` - The Amazon Resource Name (ARN) of the appstream fleet. From 889681243dc1507d02d3d81cb5bfd3bf92e78a9e Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 5 Aug 2021 18:45:37 -0600 Subject: [PATCH 02/20] refactor --- aws/resource_aws_appstream_stack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index 79e72791876..16f2c161c1a 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -293,7 +293,7 @@ func resourceAwsAppstreamStackUpdate(ctx context.Context, d *schema.ResourceData svc := meta.(*AWSClient).appstreamconn UpdateStackInputOpts := &appstream.UpdateStackInput{ - Name: aws.String(naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string))), + Name: aws.String(d.Id()), } if d.HasChange("description") { From e157f8dc12748a9ed3afe26176116855f09ec57a Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 5 Aug 2021 18:55:22 -0600 Subject: [PATCH 03/20] refactor --- aws/resource_aws_appstream_stack.go | 44 ++++++++++++++++++----------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index 16f2c161c1a..b7656ad2e5e 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -3,7 +3,6 @@ package aws import ( "context" "fmt" - "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/appstream" @@ -256,6 +255,9 @@ func resourceAwsAppstreamStackRead(ctx context.Context, d *schema.ResourceData, if err = d.Set("user_settings", flattenUserSettings(v.UserSettings)); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) } + if err = d.Set("application_settings", flattenApplicationSettings(v.ApplicationSettings)); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) + } d.Set("name", v.Name) d.Set("description", v.Description) @@ -270,10 +272,7 @@ func resourceAwsAppstreamStackRead(ctx context.Context, d *schema.ResourceData, if err != nil { return diag.FromErr(fmt.Errorf("error listing stack tags for AppStream Stack (%s): %w", d.Id(), err)) } - if tg.Tags == nil { - log.Printf("[DEBUG] Apsstream Stack tags (%s) not found", d.Id()) - return nil - } + tags := keyvaluetags.AppstreamKeyValueTags(tg.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) if err = d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -289,43 +288,56 @@ func resourceAwsAppstreamStackRead(ctx context.Context, d *schema.ResourceData, } func resourceAwsAppstreamStackUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).appstreamconn - svc := meta.(*AWSClient).appstreamconn - - UpdateStackInputOpts := &appstream.UpdateStackInput{ + input := &appstream.UpdateStackInput{ Name: aws.String(d.Id()), } if d.HasChange("description") { - UpdateStackInputOpts.Description = aws.String(d.Get("description").(string)) + input.Description = aws.String(d.Get("description").(string)) } if d.HasChange("display_name") { - UpdateStackInputOpts.DisplayName = aws.String(d.Get("display_name").(string)) + input.DisplayName = aws.String(d.Get("display_name").(string)) } if d.HasChange("feedback_url") { - UpdateStackInputOpts.FeedbackURL = aws.String(d.Get("feedback_url").(string)) + input.FeedbackURL = aws.String(d.Get("feedback_url").(string)) } if d.HasChange("redirect_url") { - UpdateStackInputOpts.RedirectURL = aws.String(d.Get("redirect_url").(string)) + input.RedirectURL = aws.String(d.Get("redirect_url").(string)) } if d.HasChange("user_settings") { - UpdateStackInputOpts.UserSettings = expandUserSettings(d.Get("user_settings").(*schema.Set).List()) + input.UserSettings = expandUserSettings(d.Get("user_settings").(*schema.Set).List()) } if d.HasChange("application_settings") { - UpdateStackInputOpts.ApplicationSettings = expandApplicationSettings(d.Get("application_settings").(*schema.Set).List()) + input.ApplicationSettings = expandApplicationSettings(d.Get("application_settings").(*schema.Set).List()) } - _, err := svc.UpdateStack(UpdateStackInputOpts) + if d.HasChange("access_endpoints") { + input.AccessEndpoints = expandAccessEndpoints(d.Get("access_endpoints").(*schema.Set).List()) + } + + resp, err := conn.UpdateStack(input) if err != nil { diag.FromErr(fmt.Errorf("error updating Appstream Stack (%s): %w", d.Id(), err)) } + + if d.HasChange("tags") { + arn := aws.StringValue(resp.Stack.Arn) + + o, n := d.GetChange("tags") + if err := keyvaluetags.AppstreamUpdateTags(conn, arn, o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating Appstream Stack tags (%s): %w", d.Id(), err)) + } + } + return resourceAwsAppstreamStackRead(ctx, d, meta) } @@ -399,7 +411,7 @@ func expandApplicationSettings(applicationSettings []interface{}) *appstream.App return applicationSetting } -func flattenApplicationSettings(applicationSettings *appstream.ApplicationSettings) []interface{} { +func flattenApplicationSettings(applicationSettings *appstream.ApplicationSettingsResponse) []interface{} { if applicationSettings == nil { return nil } From 414ddb12e24109867df784108dbe95f491fb4a22 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Fri, 6 Aug 2021 08:37:01 -0600 Subject: [PATCH 04/20] added validation for disappears --- aws/provider.go | 2 +- aws/resource_aws_appstream_stack.go | 57 ++++++++++++++++++------ aws/resource_aws_appstream_stack_test.go | 7 ++- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/aws/provider.go b/aws/provider.go index 937cd7691e6..f6179a8a394 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -526,7 +526,7 @@ func Provider() *schema.Provider { "aws_apprunner_connection": resourceAwsAppRunnerConnection(), "aws_apprunner_custom_domain_association": resourceAwsAppRunnerCustomDomainAssociation(), "aws_apprunner_service": resourceAwsAppRunnerService(), - "aws_appstream_stack": resourceAwsAppstreamStack(), + "aws_appstream_stack": resourceAwsAppStreamStack(), "aws_appsync_api_key": resourceAwsAppsyncApiKey(), "aws_appsync_datasource": resourceAwsAppsyncDatasource(), "aws_appsync_function": resourceAwsAppsyncFunction(), diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index b7656ad2e5e..db5a31b9116 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -3,22 +3,26 @@ package aws import ( "context" "fmt" + "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/appstream" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" ) -func resourceAwsAppstreamStack() *schema.Resource { +func resourceAwsAppStreamStack() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceAwsAppstreamStackCreate, - ReadWithoutTimeout: resourceAwsAppstreamStackRead, - UpdateWithoutTimeout: resourceAwsAppstreamStackUpdate, - DeleteWithoutTimeout: resourceAwsAppstreamStackDelete, + CreateWithoutTimeout: resourceAwsAppStreamStackCreate, + ReadWithoutTimeout: resourceAwsAppStreamStackRead, + UpdateWithoutTimeout: resourceAwsAppStreamStackUpdate, + DeleteWithoutTimeout: resourceAwsAppStreamStackDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -172,7 +176,7 @@ func resourceAwsAppstreamStack() *schema.Resource { } } -func resourceAwsAppstreamStackCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceAwsAppStreamStackCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).appstreamconn input := &appstream.CreateStackInput{ Name: aws.String(naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string))), @@ -221,23 +225,46 @@ func resourceAwsAppstreamStackCreate(ctx context.Context, d *schema.ResourceData input.Tags = tags.IgnoreAws().AppstreamTags() } - resp, err := conn.CreateStackWithContext(ctx, input) + var err error + var output *appstream.CreateStackOutput + err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { + output, err = conn.CreateStackWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + output, err = conn.CreateStackWithContext(ctx, input) + } + if err != nil { return diag.FromErr(fmt.Errorf("error creating Appstream Stack (%s): %w", d.Id(), err)) } - d.SetId(aws.StringValue(resp.Stack.Name)) + d.SetId(aws.StringValue(output.Stack.Name)) - return resourceAwsAppstreamStackRead(ctx, d, meta) + return resourceAwsAppStreamStackRead(ctx, d, meta) } -func resourceAwsAppstreamStackRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceAwsAppStreamStackRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).appstreamconn defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig resp, err := conn.DescribeStacksWithContext(ctx, &appstream.DescribeStacksInput{Names: []*string{aws.String(d.Id())}}) + if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Appstream Stack (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } if err != nil { return diag.FromErr(fmt.Errorf("error reading Appstream Stack (%s): %w", d.Id(), err)) @@ -287,7 +314,7 @@ func resourceAwsAppstreamStackRead(ctx context.Context, d *schema.ResourceData, return nil } -func resourceAwsAppstreamStackUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceAwsAppStreamStackUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).appstreamconn input := &appstream.UpdateStackInput{ @@ -328,7 +355,6 @@ func resourceAwsAppstreamStackUpdate(ctx context.Context, d *schema.ResourceData diag.FromErr(fmt.Errorf("error updating Appstream Stack (%s): %w", d.Id(), err)) } - if d.HasChange("tags") { arn := aws.StringValue(resp.Stack.Arn) @@ -338,16 +364,19 @@ func resourceAwsAppstreamStackUpdate(ctx context.Context, d *schema.ResourceData } } - return resourceAwsAppstreamStackRead(ctx, d, meta) + return resourceAwsAppStreamStackRead(ctx, d, meta) } -func resourceAwsAppstreamStackDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceAwsAppStreamStackDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).appstreamconn _, err := conn.DeleteStackWithContext(ctx, &appstream.DeleteStackInput{ Name: aws.String(d.Id()), }) if err != nil { + if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { + return nil + } return diag.FromErr(fmt.Errorf("error deleting Appstream Stack (%s): %w", d.Id(), err)) } return nil diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go index 9ec496ceb1a..6b7180f7ada 100644 --- a/aws/resource_aws_appstream_stack_test.go +++ b/aws/resource_aws_appstream_stack_test.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/appstream" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -53,7 +54,7 @@ func testAccAwsAppStreamStack_disappears(t *testing.T) { Config: testAccAwsAppStreamStackConfigBasic(stackName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), - testAccCheckResourceDisappears(testAccProvider, resourceAwsAppstreamStack(), resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppStreamStack(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -126,6 +127,10 @@ func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { resp, err := conn.DescribeStacks(&appstream.DescribeStacksInput{Names: []*string{aws.String(rs.Primary.ID)}}) + if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { + continue + } + if err != nil { return err } From 547fa83743c355e39e562fba2993d69d7345621f Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Fri, 6 Aug 2021 08:48:51 -0600 Subject: [PATCH 05/20] fixes a linter error --- aws/resource_aws_appstream_stack.go | 1 - 1 file changed, 1 deletion(-) diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index db5a31b9116..f735a75a03b 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -309,7 +309,6 @@ func resourceAwsAppStreamStackRead(ctx context.Context, d *schema.ResourceData, if err = d.Set("tags_all", tags.Map()); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "tags_all", d.Id(), err)) } - return nil } return nil } From 60c067bd6099b00d920afbbece67abefc15cbed0 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Fri, 6 Aug 2021 09:42:51 -0600 Subject: [PATCH 06/20] fixes a linter error --- aws/resource_aws_appstream_stack_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go index 6b7180f7ada..f6b65c99de8 100644 --- a/aws/resource_aws_appstream_stack_test.go +++ b/aws/resource_aws_appstream_stack_test.go @@ -147,7 +147,7 @@ func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { func testAccAwsAppStreamStackConfigBasic(stackName string) string { return fmt.Sprintf(` resource "aws_appstream_stack" "test_fleet" { - name = %[1]q + name = %[1]q } `, stackName) } @@ -155,12 +155,12 @@ resource "aws_appstream_stack" "test_fleet" { func testAccAwsAppStreamStackConfigWithTags(stackName, description string) string { return fmt.Sprintf(` resource "aws_appstream_stack" "test" { - name = %[1]q + name = %[1]q compute_capacity { desired_instances = 1 } - description = %[2]q - display_name = %[1]q + description = %[2]q + display_name = %[1]q storage_connectors { connector_type = "HOMEFOLDERS" } From e39f9205bf62fddeca101d00edac780d905a4467 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Tue, 10 Aug 2021 20:03:24 -0600 Subject: [PATCH 07/20] fixes for test --- aws/resource_aws_appstream_stack.go | 56 ++++- aws/resource_aws_appstream_stack_test.go | 212 ++++++++++++++++--- aws/resource_aws_appstream_test.go | 9 +- website/docs/r/appstream_stack.html.markdown | 16 +- 4 files changed, 243 insertions(+), 50 deletions(-) diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index f735a75a03b..f5d337243f8 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -17,6 +17,10 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" ) +var ( + flagDiffUserSettings = false +) + func resourceAwsAppStreamStack() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceAwsAppStreamStackCreate, @@ -140,17 +144,19 @@ func resourceAwsAppStreamStack() *schema.Resource { }, "resource_identifier": { Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ValidateFunc: validation.StringLenBetween(1, 2048), }, }, }, }, "user_settings": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - MinItems: 1, + Type: schema.TypeSet, + Optional: true, + Computed: true, + MinItems: 1, + DiffSuppressFunc: suppressAppsStreamStackUserSettings, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "action": { @@ -170,6 +176,10 @@ func resourceAwsAppStreamStack() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "created_time": { + Type: schema.TypeString, + Computed: true, + }, "tags": tagsSchemaForceNew(), "tags_all": tagsSchemaComputed(), }, @@ -285,13 +295,16 @@ func resourceAwsAppStreamStackRead(ctx context.Context, d *schema.ResourceData, if err = d.Set("application_settings", flattenApplicationSettings(v.ApplicationSettings)); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) } + if err = d.Set("embed_host_domains", flattenStringList(v.EmbedHostDomains)); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) + } - d.Set("name", v.Name) d.Set("description", v.Description) d.Set("display_name", v.DisplayName) d.Set("feedback_url", v.FeedbackURL) d.Set("redirect_url", v.RedirectURL) d.Set("arn", v.Arn) + d.Set("created_time", aws.TimeValue(v.CreatedTime).Format(time.RFC3339)) tg, err := conn.ListTagsForResource(&appstream.ListTagsForResourceInput{ ResourceArn: v.Arn, @@ -337,7 +350,7 @@ func resourceAwsAppStreamStackUpdate(ctx context.Context, d *schema.ResourceData } if d.HasChange("user_settings") { - input.UserSettings = expandUserSettings(d.Get("user_settings").(*schema.Set).List()) + input.UserSettings = expandUserSettings(d.Get("user_settings").([]interface{})) } if d.HasChange("application_settings") { @@ -378,6 +391,14 @@ func resourceAwsAppStreamStackDelete(ctx context.Context, d *schema.ResourceData } return diag.FromErr(fmt.Errorf("error deleting Appstream Stack (%s): %w", d.Id(), err)) } + + // Will wait to finish to delete because after delete it makes a stack inactive then it deletes + time.Sleep(15 * time.Second) + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading Appstream Stack (%s): %w", d.Id(), err)) + } + return nil } @@ -446,7 +467,7 @@ func flattenApplicationSettings(applicationSettings *appstream.ApplicationSettin applicationSetting := map[string]interface{}{} applicationSetting["enabled"] = aws.BoolValue(applicationSettings.Enabled) - applicationSetting["SettingsGroup"] = aws.StringValue(applicationSettings.SettingsGroup) + applicationSetting["settings_group"] = aws.StringValue(applicationSettings.SettingsGroup) return []interface{}{applicationSetting} } @@ -464,10 +485,10 @@ func expandStorageConnectors(storageConnectors []interface{}) []*appstream.Stora connector := &appstream.StorageConnector{ ConnectorType: aws.String(v1["connector_type"].(string)), } - if v2, ok := v1["domains"]; ok { + if v2, ok := v1["domains"]; ok && len(v2.([]interface{})) > 0 { connector.Domains = expandStringList(v2.([]interface{})) } - if v2, ok := v1["resource_identifier"]; ok { + if v2, ok := v1["resource_identifier"]; ok && v2.(string) != "" { connector.ResourceIdentifier = aws.String(v2.(string)) } @@ -532,3 +553,18 @@ func flattenUserSettings(userSettings []*appstream.UserSetting) []map[string]int return users } + +func suppressAppsStreamStackUserSettings(k, old, new string, d *schema.ResourceData) bool { + count := len(d.Get("user_settings").(*schema.Set).List()) + defaultCount := len(appstream.Action_Values()) + + if count == defaultCount { + flagDiffUserSettings = false + } + + if count != defaultCount && (fmt.Sprintf("%d", count) == new && fmt.Sprintf("%d", defaultCount) == old) { + flagDiffUserSettings = true + } + + return flagDiffUserSettings +} diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go index f6b65c99de8..96142775dbe 100644 --- a/aws/resource_aws_appstream_stack_test.go +++ b/aws/resource_aws_appstream_stack_test.go @@ -7,15 +7,14 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/appstream" "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" ) func testAccAwsAppStreamStack_basic(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" - stackName := acctest.RandomWithPrefix("tf-acc-test") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -24,13 +23,70 @@ func testAccAwsAppStreamStack_basic(t *testing.T) { ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsAppStreamStackConfigBasic(stackName), + Config: testAccAwsAppStreamStackConfigNameGenerated(), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + testAccCheckResourceAttrRfc3339(resourceName, "created_time"), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppStreamStack_Name_Generated(t *testing.T) { + var stackOutput appstream.Stack + resourceName := "aws_appstream_stack.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsAppStreamStackDestroy, + ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsAppStreamStackConfigNameGenerated(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppStreamStack_NamePrefix(t *testing.T) { + var stackOutput appstream.Stack + resourceName := "aws_appstream_stack.test" + namePrefix := "tf-acc-test-prefix-" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsAppStreamStackDestroy, + ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsAppStreamStackConfigNamePrefix(namePrefix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + naming.TestCheckResourceAttrNameFromPrefix(resourceName, "name", namePrefix), + resource.TestCheckResourceAttr(resourceName, "name_prefix", namePrefix), ), }, { - Config: testAccAwsAppStreamStackConfigBasic(stackName), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -42,7 +98,6 @@ func testAccAwsAppStreamStack_basic(t *testing.T) { func testAccAwsAppStreamStack_disappears(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" - stackName := acctest.RandomWithPrefix("tf-acc-test") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -51,7 +106,7 @@ func testAccAwsAppStreamStack_disappears(t *testing.T) { ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsAppStreamStackConfigBasic(stackName), + Config: testAccAwsAppStreamStackConfigNameGenerated(), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), testAccCheckResourceDisappears(testAccProvider, resourceAwsAppStreamStack(), resourceName), @@ -62,11 +117,52 @@ func testAccAwsAppStreamStack_disappears(t *testing.T) { }) } +func testAccAwsAppStreamStack_complete(t *testing.T) { + var stackOutput appstream.Stack + resourceName := "aws_appstream_stack.test" + description := "Description of a test" + descriptionUpdated := "Updated Description of a test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsAppStreamStackDestroy, + ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsAppStreamStackConfigComplete(description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + testAccCheckResourceAttrRfc3339(resourceName, "created_time"), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + resource.TestCheckResourceAttr(resourceName, "description", description), + ), + }, + { + Config: testAccAwsAppStreamStackConfigComplete(descriptionUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + testAccCheckResourceAttrRfc3339(resourceName, "created_time"), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + resource.TestCheckResourceAttr(resourceName, "description", descriptionUpdated), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccAwsAppStreamStack_withTags(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" - stackName := acctest.RandomWithPrefix("tf-acc-test") - description := "Description of a fleet" + description := "Description of a test" + descriptionUpdated := "Updated Description of a test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -75,9 +171,23 @@ func testAccAwsAppStreamStack_withTags(t *testing.T) { ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsAppStreamStackConfigWithTags(stackName, description), + Config: testAccAwsAppStreamStackConfigComplete(description), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + testAccCheckResourceAttrRfc3339(resourceName, "created_time"), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + resource.TestCheckResourceAttr(resourceName, "description", description), + ), + }, + { + Config: testAccAwsAppStreamStackConfigWithTags(descriptionUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + testAccCheckResourceAttrRfc3339(resourceName, "created_time"), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + resource.TestCheckResourceAttr(resourceName, "description", descriptionUpdated), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), @@ -108,7 +218,7 @@ func testAccCheckAwsAppStreamStackExists(resourceName string, appStreamStack *ap } if resp == nil && len(resp.Stacks) == 0 { - return fmt.Errorf("appstream fleet %q does not exist", rs.Primary.ID) + return fmt.Errorf("appstream stack %q does not exist", rs.Primary.ID) } *appStreamStack = *resp.Stacks[0] @@ -136,7 +246,7 @@ func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { } if resp != nil && len(resp.Stacks) > 0 { - return fmt.Errorf("appstream fleet %q still exists", rs.Primary.ID) + return fmt.Errorf("appstream stack %q still exists", rs.Primary.ID) } } @@ -144,41 +254,85 @@ func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { } -func testAccAwsAppStreamStackConfigBasic(stackName string) string { +func testAccAwsAppStreamStackConfigNameGenerated() string { + return fmt.Sprintf(` +resource "aws_appstream_stack" "test" {} +`) +} + +func testAccAwsAppStreamStackConfigNamePrefix(stackName string) string { return fmt.Sprintf(` -resource "aws_appstream_stack" "test_fleet" { - name = %[1]q +resource "aws_appstream_stack" "test" { + name_prefix = %[1]q } `, stackName) } -func testAccAwsAppStreamStackConfigWithTags(stackName, description string) string { +func testAccAwsAppStreamStackConfigComplete(description string) string { return fmt.Sprintf(` resource "aws_appstream_stack" "test" { - name = %[1]q - compute_capacity { - desired_instances = 1 + description = %[1]q + storage_connectors { + connector_type = "HOMEFOLDERS" + } + user_settings { + action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" + permission = "ENABLED" + } + user_settings { + action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" + permission = "ENABLED" + } + user_settings { + action = "FILE_UPLOAD" + permission = "ENABLED" } - description = %[2]q - display_name = %[1]q + user_settings { + action = "FILE_DOWNLOAD" + permission = "ENABLED" + } + application_settings { + enabled = true + settings_group = "SettingsGroup" + } +} +`, description) +} + +func testAccAwsAppStreamStackConfigWithTags(description string) string { + return fmt.Sprintf(` +resource "aws_appstream_stack" "test" { + description = %[1]q storage_connectors { connector_type = "HOMEFOLDERS" } user_settings { - action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" - enabled = true + action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" + permission = "ENABLED" + } + user_settings { + action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" + permission = "ENABLED" + } + user_settings { + action = "FILE_UPLOAD" + permission = "DISABLED" + } + user_settings { + action = "FILE_DOWNLOAD" + permission = "ENABLED" } user_settings { - action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" - enabled = true + action = "PRINTING_TO_LOCAL_DEVICE" + permission = "ENABLED" } user_settings { - action = "FILE_UPLOAD" - enabled = true + action = "DOMAIN_PASSWORD_SIGNIN" + permission = "ENABLED" } user_settings { - action = "FILE_DOWNLOAD" - enabled = true + action = "DOMAIN_SMART_CARD_SIGNIN" + permission = "ENABLED" } application_settings { enabled = true @@ -188,5 +342,5 @@ resource "aws_appstream_stack" "test" { Key = "value" } } -`, stackName, description) +`, description) } diff --git a/aws/resource_aws_appstream_test.go b/aws/resource_aws_appstream_test.go index 0e57e829c7c..5e50f02b79c 100644 --- a/aws/resource_aws_appstream_test.go +++ b/aws/resource_aws_appstream_test.go @@ -7,9 +7,12 @@ import ( func TestAccAWSAppStreamResource_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "Stack": { - "basic": testAccAwsAppStreamStack_basic, - "tags": testAccAwsAppStreamStack_withTags, - "disappears": testAccAwsAppStreamStack_disappears, + "basic": testAccAwsAppStreamStack_basic, + "name_generated": testAccAwsAppStreamStack_Name_Generated, + "name_prefix": testAccAwsAppStreamStack_NamePrefix, + "complete": testAccAwsAppStreamStack_complete, + "tags": testAccAwsAppStreamStack_withTags, + "disappears": testAccAwsAppStreamStack_disappears, }, } diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index 490be4803f5..afa0198078b 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -23,20 +23,20 @@ resource "aws_appstream_stack" "appstream_stack" { connector_type = "HOMEFOLDERS" } user_settings { - action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" - enabled = true + action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" + permission = "ENABLED" } user_settings { - action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" - enabled = true + action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" + permission = "ENABLED" } user_settings { - action = "FILE_UPLOAD" - enabled = true + action = "FILE_UPLOAD" + permission = "ENABLED" } user_settings { - action = "FILE_DOWNLOAD" - enabled = true + action = "FILE_DOWNLOAD" + permission = "ENABLED" } application_settings { enabled = true From 83f3e7ee90cf234ebc8b54a2af3c1eb8fdec828c Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 12 Aug 2021 11:14:28 -0600 Subject: [PATCH 08/20] updated docs --- website/docs/r/appstream_stack.html.markdown | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index afa0198078b..78d86bbb5a4 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -53,18 +53,23 @@ resource "aws_appstream_stack" "appstream_stack" { The following arguments are supported: -* `name` - (Required) The name of the AppStream stack, used as the stack's identifier. Only allows alphanumeric, hypen, underscore and period. +* `name` - (Required) The name of the AppStream stack, used as the stack's identifier. * `description` - (Optional) Description for the AppStream stack. -* `display_name` - (Optional) Human-readable friendly name for the AppStream stack. -* `redirect_url` - (Optional) URL to redirect at end of session. -* `feedback_url` - (Optional) URL for users to submit feedback. -* `storage_connectors` - (Optional) Nested block of storage connectors. - * `storage_connectors` - (Optional) Nested block of storage connectors. - * `storage_connectors` - (Optional) Nested block of storage connectors. -* `user_settings` - (Optional) Nested block of AppStream user settings. +* `display_name` - (Optional) The stack name to display. +* `embed_host_domains` - (Optional) The domains where AppStream 2.0 streaming sessions can be embedded in an iframe. You must approve the domains that you want to host embedded AppStream 2.0 streaming sessions. +* `redirect_url` - (Optional) The URL that users are redirected to after their streaming session ends. +* `feedback_url` - (Optional) The URL that users are redirected to after they click the Send Feedback link. If no URL is specified, no Send Feedback link is displayed. . +* `storage_connectors` - (Optional) The storage connectors to enable. + * `connector_type` - (Required) The type of storage connector. Valid values are: `HOMEFOLDERS`, `GOOGLE_DRIVE`, `ONE_DRIVE` + * `domains` - (Optional) The names of the domains for the account. + * `resource_identifier` - (Optional) The ARN of the storage connector. +* `user_settings` - (Optional) The actions that are enabled or disabled for users during their streaming sessions. By default, these actions are enabled. + * `action` - (Required) The action that is enabled or disabled. Valid values are: `CLIPBOARD_COPY_FROM_LOCAL_DEVICE`, `CLIPBOARD_COPY_TO_LOCAL_DEVICE`, `FILE_UPLOAD`,`FILE_DOWNLOAD`,`PRINTING_TO_LOCAL_DEVICE`,`DOMAIN_PASSWORD_SIGNIN`,`DOMAIN_SMART_CARD_SIGNIN`, + * `permission` - (Required) Indicates whether the action is enabled or disabled. Valid values are: `ENABLED` , `DISABLED` * `application_settings` - (Optional) settings for application settings persistence. ## Attributes Reference -* `id` - The unique identifier (ID) of the appstream fleet. -* `arn` - The Amazon Resource Name (ARN) of the appstream fleet. +* `id` - unique identifier (ID) of the appstream fleet. +* `arn` - Amazon Resource Name (ARN) of the appstream fleet. +* `created_time` - The date and time, in UTC and extended RFC 3339 format, when the stack was created. From af41b5b36a4cb72bf58f5d2236359b8cd831ad8f Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 12 Aug 2021 11:20:19 -0600 Subject: [PATCH 09/20] updated docs --- website/docs/r/appstream_stack.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index 78d86bbb5a4..c4213a21861 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -70,6 +70,6 @@ The following arguments are supported: ## Attributes Reference -* `id` - unique identifier (ID) of the appstream fleet. +* `id` - Unique identifier (ID) of the appstream fleet. * `arn` - Amazon Resource Name (ARN) of the appstream fleet. * `created_time` - The date and time, in UTC and extended RFC 3339 format, when the stack was created. From 336df92265f70cb9e773f033ce0a2a48c68c7d65 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 12 Aug 2021 14:59:55 -0600 Subject: [PATCH 10/20] fixes linter --- aws/resource_aws_appstream_stack.go | 5 ++-- aws/resource_aws_appstream_stack_test.go | 4 +-- website/allowed-subcategories.txt | 1 + website/docs/r/appstream_stack.html.markdown | 31 +++++++++++++------- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index f5d337243f8..a0023868be8 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appstream/waiter" ) var ( @@ -237,7 +238,7 @@ func resourceAwsAppStreamStackCreate(ctx context.Context, d *schema.ResourceData var err error var output *appstream.CreateStackOutput - err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { + err = resource.RetryContext(ctx, waiter.StackOperationTimeout, func() *resource.RetryError { output, err = conn.CreateStackWithContext(ctx, input) if err != nil { if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { @@ -270,7 +271,7 @@ func resourceAwsAppStreamStackRead(ctx context.Context, d *schema.ResourceData, ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig resp, err := conn.DescribeStacksWithContext(ctx, &appstream.DescribeStacksInput{Names: []*string{aws.String(d.Id())}}) - if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { log.Printf("[WARN] Appstream Stack (%s) not found, removing from state", d.Id()) d.SetId("") return nil diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go index 96142775dbe..b99a0ee0602 100644 --- a/aws/resource_aws_appstream_stack_test.go +++ b/aws/resource_aws_appstream_stack_test.go @@ -255,9 +255,9 @@ func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { } func testAccAwsAppStreamStackConfigNameGenerated() string { - return fmt.Sprintf(` + return ` resource "aws_appstream_stack" "test" {} -`) +` } func testAccAwsAppStreamStackConfigNamePrefix(stackName string) string { diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index 7fc62ac6c97..849f3300765 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -9,6 +9,7 @@ AppConfig AppMesh App Runner AppSync +AppStream Application Autoscaling Athena Audit Manager diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index c4213a21861..415fe960571 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -3,7 +3,7 @@ subcategory: "AppStream" layout: "aws" page_title: "AWS: aws_appstream_stack" description: |- -Provides an AppStream stack + Provides an AppStream stack --- # Resource: aws_appstream_stack @@ -12,7 +12,7 @@ Provides an AppStream stack. ## Example Usage -```hcl +```terraform resource "aws_appstream_stack" "appstream_stack" { name = "stack name" description = "stack description" @@ -53,23 +53,34 @@ resource "aws_appstream_stack" "appstream_stack" { The following arguments are supported: -* `name` - (Required) The name of the AppStream stack, used as the stack's identifier. +* `name` - (Optional) A unique name for the AppStream stack. +* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`. * `description` - (Optional) Description for the AppStream stack. * `display_name` - (Optional) The stack name to display. * `embed_host_domains` - (Optional) The domains where AppStream 2.0 streaming sessions can be embedded in an iframe. You must approve the domains that you want to host embedded AppStream 2.0 streaming sessions. * `redirect_url` - (Optional) The URL that users are redirected to after their streaming session ends. * `feedback_url` - (Optional) The URL that users are redirected to after they click the Send Feedback link. If no URL is specified, no Send Feedback link is displayed. . -* `storage_connectors` - (Optional) The storage connectors to enable. - * `connector_type` - (Required) The type of storage connector. Valid values are: `HOMEFOLDERS`, `GOOGLE_DRIVE`, `ONE_DRIVE` - * `domains` - (Optional) The names of the domains for the account. - * `resource_identifier` - (Optional) The ARN of the storage connector. -* `user_settings` - (Optional) The actions that are enabled or disabled for users during their streaming sessions. By default, these actions are enabled. - * `action` - (Required) The action that is enabled or disabled. Valid values are: `CLIPBOARD_COPY_FROM_LOCAL_DEVICE`, `CLIPBOARD_COPY_TO_LOCAL_DEVICE`, `FILE_UPLOAD`,`FILE_DOWNLOAD`,`PRINTING_TO_LOCAL_DEVICE`,`DOMAIN_PASSWORD_SIGNIN`,`DOMAIN_SMART_CARD_SIGNIN`, - * `permission` - (Required) Indicates whether the action is enabled or disabled. Valid values are: `ENABLED` , `DISABLED` +* `storage_connectors` - (Optional) The storage connectors to enable. (documented below) +* `user_settings` - (Optional) The actions that are enabled or disabled for users during their streaming sessions. By default, these actions are enabled. (documented below) * `application_settings` - (Optional) settings for application settings persistence. + +The `storage_connectors` object supports the following: + +* `connector_type` - (Required) The type of storage connector. Valid values are: `HOMEFOLDERS`, `GOOGLE_DRIVE`, `ONE_DRIVE` +* `domains` - (Optional) The names of the domains for the account. +* `resource_identifier` - (Optional) The ARN of the storage connector. + +The `user_settings` object supports the following: + +* `action` - (Required) The action that is enabled or disabled. Valid values are: `CLIPBOARD_COPY_FROM_LOCAL_DEVICE`, `CLIPBOARD_COPY_TO_LOCAL_DEVICE`, `FILE_UPLOAD`,`FILE_DOWNLOAD`,`PRINTING_TO_LOCAL_DEVICE`,`DOMAIN_PASSWORD_SIGNIN`,`DOMAIN_SMART_CARD_SIGNIN`, +* `permission` - (Required) Indicates whether the action is enabled or disabled. Valid values are: `ENABLED` , `DISABLED` + + ## Attributes Reference +In addition to all arguments above, the following attributes are exported: + * `id` - Unique identifier (ID) of the appstream fleet. * `arn` - Amazon Resource Name (ARN) of the appstream fleet. * `created_time` - The date and time, in UTC and extended RFC 3339 format, when the stack was created. From 9f0d39670d1d0c0190fa127c89d2cfbeaef98f9d Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 12 Aug 2021 15:10:20 -0600 Subject: [PATCH 11/20] fixes typo en docs --- website/docs/r/appstream_stack.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index 415fe960571..ce7dc42e490 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -81,6 +81,6 @@ The `user_settings` object supports the following: In addition to all arguments above, the following attributes are exported: -* `id` - Unique identifier (ID) of the appstream fleet. -* `arn` - Amazon Resource Name (ARN) of the appstream fleet. +* `id` - Unique identifier (ID) of the appstream stack. +* `arn` - Amazon Resource Name (ARN) of the appstream stack. * `created_time` - The date and time, in UTC and extended RFC 3339 format, when the stack was created. From 3814d69a640fcd3ad642b59c5392efcb3a3bd288 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 12 Aug 2021 15:12:21 -0600 Subject: [PATCH 12/20] added changelog --- .changelog/20547.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/20547.txt diff --git a/.changelog/20547.txt b/.changelog/20547.txt new file mode 100644 index 00000000000..99c8c3aee3c --- /dev/null +++ b/.changelog/20547.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_appstream_stack +``` \ No newline at end of file From 5e34a0d17897dbed17cef99c00f59e3f473f6b80 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 12 Aug 2021 15:15:27 -0600 Subject: [PATCH 13/20] added waiter timeout --- aws/internal/service/appstream/waiter/waiter.go | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 aws/internal/service/appstream/waiter/waiter.go diff --git a/aws/internal/service/appstream/waiter/waiter.go b/aws/internal/service/appstream/waiter/waiter.go new file mode 100644 index 00000000000..85509003b5a --- /dev/null +++ b/aws/internal/service/appstream/waiter/waiter.go @@ -0,0 +1,10 @@ +package waiter + +import ( + "time" +) + +const ( + // StackOperationTimeout Maximum amount of time to wait for Stack operation eventual consistency + StackOperationTimeout = 4 * time.Minute +) From c6e0a096b92a357265e573f5a6fdf5d7ac1303fd Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Thu, 12 Aug 2021 15:30:46 -0600 Subject: [PATCH 14/20] fixes linter --- aws/internal/service/appstream/waiter/waiter.go | 2 ++ aws/resource_aws_appstream_stack.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/aws/internal/service/appstream/waiter/waiter.go b/aws/internal/service/appstream/waiter/waiter.go index 85509003b5a..970662b4c57 100644 --- a/aws/internal/service/appstream/waiter/waiter.go +++ b/aws/internal/service/appstream/waiter/waiter.go @@ -7,4 +7,6 @@ import ( const ( // StackOperationTimeout Maximum amount of time to wait for Stack operation eventual consistency StackOperationTimeout = 4 * time.Minute + // StackSleep Maximum amount of time to sleep for Stack operation after delete + StackSleep = 15 * time.Second ) diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index a0023868be8..b4f15f1479b 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -394,7 +394,7 @@ func resourceAwsAppStreamStackDelete(ctx context.Context, d *schema.ResourceData } // Will wait to finish to delete because after delete it makes a stack inactive then it deletes - time.Sleep(15 * time.Second) + time.Sleep(waiter.StackSleep) if err != nil { return diag.FromErr(fmt.Errorf("error reading Appstream Stack (%s): %w", d.Id(), err)) From 3491a3bd0191e50eed28b7f455bd938fcc337f1a Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Mon, 16 Aug 2021 19:28:44 -0600 Subject: [PATCH 15/20] refactor --- aws/resource_aws_appstream_stack.go | 155 +++++++++---------- aws/resource_aws_appstream_stack_test.go | 24 +-- aws/resource_aws_appstream_test.go | 30 ---- website/docs/r/appstream_stack.html.markdown | 43 +++-- 4 files changed, 116 insertions(+), 136 deletions(-) delete mode 100644 aws/resource_aws_appstream_test.go diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index b4f15f1479b..addeb2fe621 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -71,6 +71,14 @@ func resourceAwsAppStreamStack() *schema.Resource { }, }, }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeString, + Computed: true, + }, "description": { Type: schema.TypeString, Optional: true, @@ -173,14 +181,6 @@ func resourceAwsAppStreamStack() *schema.Resource { }, }, }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "created_time": { - Type: schema.TypeString, - Computed: true, - }, "tags": tagsSchemaForceNew(), "tags_all": tagsSchemaComputed(), }, @@ -281,31 +281,30 @@ func resourceAwsAppStreamStackRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(fmt.Errorf("error reading Appstream Stack (%s): %w", d.Id(), err)) } for _, v := range resp.Stacks { - d.Set("name", v.Name) - d.Set("name_prefix", naming.NamePrefixFromName(aws.StringValue(v.Name))) if err = d.Set("access_endpoints", flattenAccessEndpoints(v.AccessEndpoints)); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "access_endpoints", d.Id(), err)) } - if err = d.Set("storage_connectors", flattenStorageConnectors(v.StorageConnectors)); err != nil { - return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "storage_connectors", d.Id(), err)) - } - if err = d.Set("user_settings", flattenUserSettings(v.UserSettings)); err != nil { - return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) - } if err = d.Set("application_settings", flattenApplicationSettings(v.ApplicationSettings)); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) } + d.Set("arn", v.Arn) + d.Set("created_time", aws.TimeValue(v.CreatedTime).Format(time.RFC3339)) + d.Set("description", v.Description) + d.Set("display_name", v.DisplayName) if err = d.Set("embed_host_domains", flattenStringList(v.EmbedHostDomains)); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) } - - d.Set("description", v.Description) - d.Set("display_name", v.DisplayName) d.Set("feedback_url", v.FeedbackURL) + d.Set("name", v.Name) + d.Set("name_prefix", naming.NamePrefixFromName(aws.StringValue(v.Name))) d.Set("redirect_url", v.RedirectURL) - d.Set("arn", v.Arn) - d.Set("created_time", aws.TimeValue(v.CreatedTime).Format(time.RFC3339)) + if err = d.Set("storage_connectors", flattenStorageConnectors(v.StorageConnectors)); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "storage_connectors", d.Id(), err)) + } + if err = d.Set("user_settings", flattenUserSettings(v.UserSettings)); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "user_settings", d.Id(), err)) + } tg, err := conn.ListTagsForResource(&appstream.ListTagsForResourceInput{ ResourceArn: v.Arn, @@ -334,6 +333,14 @@ func resourceAwsAppStreamStackUpdate(ctx context.Context, d *schema.ResourceData Name: aws.String(d.Id()), } + if d.HasChange("access_endpoints") { + input.AccessEndpoints = expandAccessEndpoints(d.Get("access_endpoints").(*schema.Set).List()) + } + + if d.HasChange("application_settings") { + input.ApplicationSettings = expandApplicationSettings(d.Get("application_settings").(*schema.Set).List()) + } + if d.HasChange("description") { input.Description = aws.String(d.Get("description").(string)) } @@ -354,14 +361,6 @@ func resourceAwsAppStreamStackUpdate(ctx context.Context, d *schema.ResourceData input.UserSettings = expandUserSettings(d.Get("user_settings").([]interface{})) } - if d.HasChange("application_settings") { - input.ApplicationSettings = expandApplicationSettings(d.Get("application_settings").(*schema.Set).List()) - } - - if d.HasChange("access_endpoints") { - input.AccessEndpoints = expandAccessEndpoints(d.Get("access_endpoints").(*schema.Set).List()) - } - resp, err := conn.UpdateStack(input) if err != nil { @@ -403,14 +402,14 @@ func resourceAwsAppStreamStackDelete(ctx context.Context, d *schema.ResourceData return nil } -func expandAccessEndpoints(accessEndpoints []interface{}) []*appstream.AccessEndpoint { - if len(accessEndpoints) == 0 { +func expandAccessEndpoints(tfList []interface{}) []*appstream.AccessEndpoint { + if len(tfList) == 0 { return nil } - var endpoints []*appstream.AccessEndpoint + var apiObjects []*appstream.AccessEndpoint - for _, v := range accessEndpoints { + for _, v := range tfList { v1 := v.(map[string]interface{}) endpoint := &appstream.AccessEndpoint{ @@ -420,67 +419,67 @@ func expandAccessEndpoints(accessEndpoints []interface{}) []*appstream.AccessEnd endpoint.VpceId = aws.String(v2.(string)) } - endpoints = append(endpoints, endpoint) + apiObjects = append(apiObjects, endpoint) } - return endpoints + return apiObjects } -func flattenAccessEndpoints(accessEndpoints []*appstream.AccessEndpoint) []map[string]interface{} { - if accessEndpoints == nil { +func flattenAccessEndpoints(apiObjects []*appstream.AccessEndpoint) []map[string]interface{} { + if apiObjects == nil { return nil } - var endpoints []map[string]interface{} + var tfList []map[string]interface{} - for _, endpoint := range accessEndpoints { - endpoints = append(endpoints, map[string]interface{}{ + for _, endpoint := range apiObjects { + tfList = append(tfList, map[string]interface{}{ "endpoint_type": aws.StringValue(endpoint.EndpointType), "vpce_id": aws.StringValue(endpoint.VpceId), }) } - return endpoints + return tfList } -func expandApplicationSettings(applicationSettings []interface{}) *appstream.ApplicationSettings { - if len(applicationSettings) == 0 { +func expandApplicationSettings(tfList []interface{}) *appstream.ApplicationSettings { + if len(tfList) == 0 { return nil } - applicationSetting := &appstream.ApplicationSettings{} + apiObject := &appstream.ApplicationSettings{} - attr := applicationSettings[0].(map[string]interface{}) + attr := tfList[0].(map[string]interface{}) if v, ok := attr["enabled"]; ok { - applicationSetting.Enabled = aws.Bool(v.(bool)) + apiObject.Enabled = aws.Bool(v.(bool)) } if v, ok := attr["settings_group"]; ok { - applicationSetting.SettingsGroup = aws.String(v.(string)) + apiObject.SettingsGroup = aws.String(v.(string)) } - return applicationSetting + return apiObject } -func flattenApplicationSettings(applicationSettings *appstream.ApplicationSettingsResponse) []interface{} { - if applicationSettings == nil { +func flattenApplicationSettings(apiObject *appstream.ApplicationSettingsResponse) []interface{} { + if apiObject == nil { return nil } - applicationSetting := map[string]interface{}{} - applicationSetting["enabled"] = aws.BoolValue(applicationSettings.Enabled) - applicationSetting["settings_group"] = aws.StringValue(applicationSettings.SettingsGroup) + tfList := map[string]interface{}{} + tfList["enabled"] = aws.BoolValue(apiObject.Enabled) + tfList["settings_group"] = aws.StringValue(apiObject.SettingsGroup) - return []interface{}{applicationSetting} + return []interface{}{tfList} } -func expandStorageConnectors(storageConnectors []interface{}) []*appstream.StorageConnector { - if len(storageConnectors) == 0 { +func expandStorageConnectors(tfList []interface{}) []*appstream.StorageConnector { + if len(tfList) == 0 { return nil } - var connectors []*appstream.StorageConnector + var apiObjects []*appstream.StorageConnector - for _, v := range storageConnectors { + for _, v := range tfList { v1 := v.(map[string]interface{}) connector := &appstream.StorageConnector{ @@ -493,38 +492,38 @@ func expandStorageConnectors(storageConnectors []interface{}) []*appstream.Stora connector.ResourceIdentifier = aws.String(v2.(string)) } - connectors = append(connectors, connector) + apiObjects = append(apiObjects, connector) } - return connectors + return apiObjects } -func flattenStorageConnectors(storageConnectors []*appstream.StorageConnector) []map[string]interface{} { - if storageConnectors == nil { +func flattenStorageConnectors(apiObjects []*appstream.StorageConnector) []map[string]interface{} { + if apiObjects == nil { return nil } - var connectors []map[string]interface{} + var tfList []map[string]interface{} - for _, connector := range storageConnectors { - connectors = append(connectors, map[string]interface{}{ + for _, connector := range apiObjects { + tfList = append(tfList, map[string]interface{}{ "connector_type": aws.StringValue(connector.ConnectorType), "domains": aws.StringValueSlice(connector.Domains), "resource_identifier": aws.StringValue(connector.ResourceIdentifier), }) } - return connectors + return tfList } -func expandUserSettings(userSettings []interface{}) []*appstream.UserSetting { - if len(userSettings) == 0 { +func expandUserSettings(tfList []interface{}) []*appstream.UserSetting { + if len(tfList) == 0 { return nil } - var users []*appstream.UserSetting + var apiObjects []*appstream.UserSetting - for _, v := range userSettings { + for _, v := range tfList { v1 := v.(map[string]interface{}) user := &appstream.UserSetting{ @@ -532,27 +531,27 @@ func expandUserSettings(userSettings []interface{}) []*appstream.UserSetting { Permission: aws.String(v1["permission"].(string)), } - users = append(users, user) + apiObjects = append(apiObjects, user) } - return users + return apiObjects } -func flattenUserSettings(userSettings []*appstream.UserSetting) []map[string]interface{} { - if userSettings == nil { +func flattenUserSettings(apiObjects []*appstream.UserSetting) []map[string]interface{} { + if apiObjects == nil { return nil } - var users []map[string]interface{} + var tfList []map[string]interface{} - for _, user := range userSettings { - users = append(users, map[string]interface{}{ + for _, user := range apiObjects { + tfList = append(tfList, map[string]interface{}{ "action": aws.StringValue(user.Action), "permission": aws.StringValue(user.Permission), }) } - return users + return tfList } func suppressAppsStreamStackUserSettings(k, old, new string, d *schema.ResourceData) bool { diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go index b99a0ee0602..b14cbeee008 100644 --- a/aws/resource_aws_appstream_stack_test.go +++ b/aws/resource_aws_appstream_stack_test.go @@ -12,11 +12,11 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" ) -func testAccAwsAppStreamStack_basic(t *testing.T) { +func TestAccAwsAppStreamStack_basic(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckAwsAppStreamStackDestroy, @@ -40,11 +40,11 @@ func testAccAwsAppStreamStack_basic(t *testing.T) { }) } -func testAccAwsAppStreamStack_Name_Generated(t *testing.T) { +func TestAccAwsAppStreamStack_nameGenerated(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckAwsAppStreamStackDestroy, @@ -67,12 +67,12 @@ func testAccAwsAppStreamStack_Name_Generated(t *testing.T) { }) } -func testAccAwsAppStreamStack_NamePrefix(t *testing.T) { +func TestAccAwsAppStreamStack_namePrefix(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" namePrefix := "tf-acc-test-prefix-" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckAwsAppStreamStackDestroy, @@ -95,11 +95,11 @@ func testAccAwsAppStreamStack_NamePrefix(t *testing.T) { }) } -func testAccAwsAppStreamStack_disappears(t *testing.T) { +func TestAccAwsAppStreamStack_disappears(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckAwsAppStreamStackDestroy, @@ -117,13 +117,13 @@ func testAccAwsAppStreamStack_disappears(t *testing.T) { }) } -func testAccAwsAppStreamStack_complete(t *testing.T) { +func TestAccAwsAppStreamStack_complete(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" description := "Description of a test" descriptionUpdated := "Updated Description of a test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckAwsAppStreamStackDestroy, @@ -158,13 +158,13 @@ func testAccAwsAppStreamStack_complete(t *testing.T) { }) } -func testAccAwsAppStreamStack_withTags(t *testing.T) { +func TestAccAwsAppStreamStack_withTags(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" description := "Description of a test" descriptionUpdated := "Updated Description of a test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: testAccCheckAwsAppStreamStackDestroy, diff --git a/aws/resource_aws_appstream_test.go b/aws/resource_aws_appstream_test.go deleted file mode 100644 index 5e50f02b79c..00000000000 --- a/aws/resource_aws_appstream_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package aws - -import ( - "testing" -) - -func TestAccAWSAppStreamResource_serial(t *testing.T) { - testCases := map[string]map[string]func(t *testing.T){ - "Stack": { - "basic": testAccAwsAppStreamStack_basic, - "name_generated": testAccAwsAppStreamStack_Name_Generated, - "name_prefix": testAccAwsAppStreamStack_NamePrefix, - "complete": testAccAwsAppStreamStack_complete, - "tags": testAccAwsAppStreamStack_withTags, - "disappears": testAccAwsAppStreamStack_disappears, - }, - } - - for group, m := range testCases { - m := m - t.Run(group, func(t *testing.T) { - for name, tc := range m { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - }) - } - }) - } -} diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index ce7dc42e490..34658831f32 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -19,9 +19,11 @@ resource "aws_appstream_stack" "appstream_stack" { display_name = "stack display name" feedback_url = "http://your-domain/feedback" redirect_url = "http://your-domain/redirect" + storage_connectors { connector_type = "HOMEFOLDERS" } + user_settings { action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" permission = "ENABLED" @@ -38,6 +40,7 @@ resource "aws_appstream_stack" "appstream_stack" { action = "FILE_DOWNLOAD" permission = "ENABLED" } + application_settings { enabled = true settings_group = "SettingsGroup" @@ -51,29 +54,28 @@ resource "aws_appstream_stack" "appstream_stack" { ## Argument Reference -The following arguments are supported: +The following arguments are optional: -* `name` - (Optional) A unique name for the AppStream stack. +* `name` - (Optional) Unique name for the AppStream stack. * `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`. * `description` - (Optional) Description for the AppStream stack. -* `display_name` - (Optional) The stack name to display. -* `embed_host_domains` - (Optional) The domains where AppStream 2.0 streaming sessions can be embedded in an iframe. You must approve the domains that you want to host embedded AppStream 2.0 streaming sessions. -* `redirect_url` - (Optional) The URL that users are redirected to after their streaming session ends. -* `feedback_url` - (Optional) The URL that users are redirected to after they click the Send Feedback link. If no URL is specified, no Send Feedback link is displayed. . -* `storage_connectors` - (Optional) The storage connectors to enable. (documented below) -* `user_settings` - (Optional) The actions that are enabled or disabled for users during their streaming sessions. By default, these actions are enabled. (documented below) +* `display_name` - (Optional) Stack name to display. +* `embed_host_domains` - (Optional) Domains where AppStream 2.0 streaming sessions can be embedded in an iframe. You must approve the domains that you want to host embedded AppStream 2.0 streaming sessions. +* `redirect_url` - (Optional) URL that users are redirected to after their streaming session ends. +* `feedback_url` - (Optional) URL that users are redirected to after they click the Send Feedback link. If no URL is specified, no Send Feedback link is displayed. . +* `storage_connectors` - (Optional) Configuration block for the storage connectors to enable. See below. +* `user_settings` - (Optional) Configuration block for the actions that are enabled or disabled for users during their streaming sessions. By default, these actions are enabled. See below. * `application_settings` - (Optional) settings for application settings persistence. +### `storage_connectors` -The `storage_connectors` object supports the following: - -* `connector_type` - (Required) The type of storage connector. Valid values are: `HOMEFOLDERS`, `GOOGLE_DRIVE`, `ONE_DRIVE` -* `domains` - (Optional) The names of the domains for the account. +* `connector_type` - (Required) Type of storage connector. Valid values are: `HOMEFOLDERS`, `GOOGLE_DRIVE`, `ONE_DRIVE` +* `domains` - (Optional) Names of the domains for the account. * `resource_identifier` - (Optional) The ARN of the storage connector. -The `user_settings` object supports the following: +### `user_settings` -* `action` - (Required) The action that is enabled or disabled. Valid values are: `CLIPBOARD_COPY_FROM_LOCAL_DEVICE`, `CLIPBOARD_COPY_TO_LOCAL_DEVICE`, `FILE_UPLOAD`,`FILE_DOWNLOAD`,`PRINTING_TO_LOCAL_DEVICE`,`DOMAIN_PASSWORD_SIGNIN`,`DOMAIN_SMART_CARD_SIGNIN`, +* `action` - (Required) Action that is enabled or disabled. Valid values are: `CLIPBOARD_COPY_FROM_LOCAL_DEVICE`, `CLIPBOARD_COPY_TO_LOCAL_DEVICE`, `FILE_UPLOAD`,`FILE_DOWNLOAD`,`PRINTING_TO_LOCAL_DEVICE`,`DOMAIN_PASSWORD_SIGNIN`,`DOMAIN_SMART_CARD_SIGNIN`, * `permission` - (Required) Indicates whether the action is enabled or disabled. Valid values are: `ENABLED` , `DISABLED` @@ -82,5 +84,14 @@ The `user_settings` object supports the following: In addition to all arguments above, the following attributes are exported: * `id` - Unique identifier (ID) of the appstream stack. -* `arn` - Amazon Resource Name (ARN) of the appstream stack. -* `created_time` - The date and time, in UTC and extended RFC 3339 format, when the stack was created. +* `arn` - ARN of the appstream stack. +* `created_time` - Date and time, in UTC and extended RFC 3339 format, when the stack was created. + + +## Import + +`aws_appstream_stack` can be imported using the id, e.g. + +``` +$ terraform import aws_appstream_stack.example stackNameExample +``` From c671b5e86f21c39c30edac3723b7b1294a353741 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Tue, 17 Aug 2021 13:57:35 -0600 Subject: [PATCH 16/20] added waiter --- .../service/appstream/finder/finder.go | 32 +++++++++++++++++++ .../service/appstream/waiter/status.go | 25 +++++++++++++++ .../service/appstream/waiter/waiter.go | 23 +++++++++++-- 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 aws/internal/service/appstream/finder/finder.go create mode 100644 aws/internal/service/appstream/waiter/status.go diff --git a/aws/internal/service/appstream/finder/finder.go b/aws/internal/service/appstream/finder/finder.go new file mode 100644 index 00000000000..a7c2dd2ad1d --- /dev/null +++ b/aws/internal/service/appstream/finder/finder.go @@ -0,0 +1,32 @@ +package finder + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appstream" +) + +// StackByName Retrieve a appstream stack by name +func StackByName(ctx context.Context, conn *appstream.AppStream, name string) (*appstream.Stack, error) { + input := &appstream.DescribeStacksInput{ + Names: []*string{aws.String(name)}, + } + + var stack *appstream.Stack + resp, err := conn.DescribeStacksWithContext(ctx, input) + if err != nil { + return nil, err + } + + if len(resp.Stacks) > 1 { + fmt.Errorf("[ERROR] got more than one stack with the name %s", name) + } + + if len(resp.Stacks) == 1 { + stack = resp.Stacks[0] + } + + return stack, nil +} diff --git a/aws/internal/service/appstream/waiter/status.go b/aws/internal/service/appstream/waiter/status.go new file mode 100644 index 00000000000..94428acc414 --- /dev/null +++ b/aws/internal/service/appstream/waiter/status.go @@ -0,0 +1,25 @@ +package waiter + +import ( + "context" + + "github.com/aws/aws-sdk-go/service/appstream" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appstream/finder" +) + +//StackState fetches the fleet and its state +func StackState(ctx context.Context, conn *appstream.AppStream, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + stack, err := finder.StackByName(ctx, conn, name) + if err != nil { + return nil, "Unknown", err + } + + if stack == nil { + return stack, "NotFound", nil + } + + return stack, "AVAILABLE", nil + } +} diff --git a/aws/internal/service/appstream/waiter/waiter.go b/aws/internal/service/appstream/waiter/waiter.go index 970662b4c57..84727bc3e3d 100644 --- a/aws/internal/service/appstream/waiter/waiter.go +++ b/aws/internal/service/appstream/waiter/waiter.go @@ -1,12 +1,31 @@ package waiter import ( + "context" "time" + + "github.com/aws/aws-sdk-go/service/appstream" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) const ( // StackOperationTimeout Maximum amount of time to wait for Stack operation eventual consistency StackOperationTimeout = 4 * time.Minute - // StackSleep Maximum amount of time to sleep for Stack operation after delete - StackSleep = 15 * time.Second ) + +// StackStateDeleted waits for a deleted stack +func StackStateDeleted(ctx context.Context, conn *appstream.AppStream, name string) (*appstream.Stack, error) { + stateConf := &resource.StateChangeConf{ + Target: []string{"NotFound", "Unknown"}, + Refresh: StackState(ctx, conn, name), + Timeout: StackOperationTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*appstream.Stack); ok { + return output, err + } + + return nil, err +} \ No newline at end of file From 5e2a8cf1aebe096c5e0090820c8de4bef3ff920d Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Tue, 17 Aug 2021 17:31:58 -0600 Subject: [PATCH 17/20] refactor --- .../service/appstream/finder/finder.go | 2 +- .../service/appstream/waiter/waiter.go | 2 +- aws/resource_aws_appstream_stack.go | 275 +++++++++++++----- aws/resource_aws_appstream_stack_test.go | 118 ++------ website/docs/r/appstream_stack.html.markdown | 8 +- 5 files changed, 239 insertions(+), 166 deletions(-) diff --git a/aws/internal/service/appstream/finder/finder.go b/aws/internal/service/appstream/finder/finder.go index a7c2dd2ad1d..8fce64010b9 100644 --- a/aws/internal/service/appstream/finder/finder.go +++ b/aws/internal/service/appstream/finder/finder.go @@ -21,7 +21,7 @@ func StackByName(ctx context.Context, conn *appstream.AppStream, name string) (* } if len(resp.Stacks) > 1 { - fmt.Errorf("[ERROR] got more than one stack with the name %s", name) + return nil, fmt.Errorf("[ERROR] got more than one stack with the name %s", name) } if len(resp.Stacks) == 1 { diff --git a/aws/internal/service/appstream/waiter/waiter.go b/aws/internal/service/appstream/waiter/waiter.go index 84727bc3e3d..765170f881b 100644 --- a/aws/internal/service/appstream/waiter/waiter.go +++ b/aws/internal/service/appstream/waiter/waiter.go @@ -28,4 +28,4 @@ func StackStateDeleted(ctx context.Context, conn *appstream.AppStream, name stri } return nil, err -} \ No newline at end of file +} diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index addeb2fe621..770aa8a2854 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -1,6 +1,7 @@ package aws import ( + "bytes" "context" "fmt" "log" @@ -13,8 +14,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appstream/waiter" ) @@ -52,6 +53,7 @@ func resourceAwsAppStreamStack() *schema.Resource { }, }, }, + Set: accessEndpointsHash, }, "application_settings": { Type: schema.TypeList, @@ -94,7 +96,7 @@ func resourceAwsAppStreamStack() *schema.Resource { ValidateFunc: validation.StringLenBetween(0, 100), }, "embed_host_domains": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Computed: true, MinItems: 1, @@ -103,6 +105,7 @@ func resourceAwsAppStreamStack() *schema.Resource { Type: schema.TypeString, ValidateFunc: validation.StringLenBetween(0, 128), }, + Set: schema.HashString, }, "feedback_url": { Type: schema.TypeString, @@ -111,18 +114,9 @@ func resourceAwsAppStreamStack() *schema.Resource { ValidateFunc: validation.StringLenBetween(0, 100), }, "name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ConflictsWith: []string{"name_prefix"}, - }, - "name_prefix": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ConflictsWith: []string{"name"}, + Type: schema.TypeString, + Required: true, + ForceNew: true, }, "redirect_url": { Type: schema.TypeString, @@ -159,6 +153,7 @@ func resourceAwsAppStreamStack() *schema.Resource { }, }, }, + Set: storageConnectorsHash, }, "user_settings": { Type: schema.TypeSet, @@ -190,7 +185,7 @@ func resourceAwsAppStreamStack() *schema.Resource { func resourceAwsAppStreamStackCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).appstreamconn input := &appstream.CreateStackInput{ - Name: aws.String(naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string))), + Name: aws.String(d.Get("name").(string)), } defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig @@ -297,7 +292,6 @@ func resourceAwsAppStreamStackRead(ctx context.Context, d *schema.ResourceData, } d.Set("feedback_url", v.FeedbackURL) d.Set("name", v.Name) - d.Set("name_prefix", naming.NamePrefixFromName(aws.StringValue(v.Name))) d.Set("redirect_url", v.RedirectURL) if err = d.Set("storage_connectors", flattenStorageConnectors(v.StorageConnectors)); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for AppStream Stack (%s): %w", "storage_connectors", d.Id(), err)) @@ -392,8 +386,13 @@ func resourceAwsAppStreamStackDelete(ctx context.Context, d *schema.ResourceData return diag.FromErr(fmt.Errorf("error deleting Appstream Stack (%s): %w", d.Id(), err)) } - // Will wait to finish to delete because after delete it makes a stack inactive then it deletes - time.Sleep(waiter.StackSleep) + if _, err = waiter.StackStateDeleted(ctx, conn, d.Id()); err != nil { + if tfawserr.ErrCodeEquals(err, appstream.ErrCodeResourceNotFoundException) { + return nil + } + + return diag.FromErr(fmt.Errorf("error waiting for Appstream Stack (%s) to be deleted: %w", d.Id(), err)) + } if err != nil { return diag.FromErr(fmt.Errorf("error reading Appstream Stack (%s): %w", d.Id(), err)) @@ -402,6 +401,21 @@ func resourceAwsAppStreamStackDelete(ctx context.Context, d *schema.ResourceData return nil } +func expandAccessEndpoint(tfMap map[string]interface{}) *appstream.AccessEndpoint { + if tfMap == nil { + return nil + } + + apiObject := &appstream.AccessEndpoint{ + EndpointType: aws.String(tfMap["endpoint_type"].(string)), + } + if v, ok := tfMap["vpce_id"]; ok { + apiObject.VpceId = aws.String(v.(string)) + } + + return apiObject +} + func expandAccessEndpoints(tfList []interface{}) []*appstream.AccessEndpoint { if len(tfList) == 0 { return nil @@ -409,67 +423,128 @@ func expandAccessEndpoints(tfList []interface{}) []*appstream.AccessEndpoint { var apiObjects []*appstream.AccessEndpoint - for _, v := range tfList { - v1 := v.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) - endpoint := &appstream.AccessEndpoint{ - EndpointType: aws.String(v1["endpoint_type"].(string)), - } - if v2, ok := v1["vpce_id"]; ok { - endpoint.VpceId = aws.String(v2.(string)) + if !ok { + continue } - apiObjects = append(apiObjects, endpoint) + apiObject := expandAccessEndpoint(tfMap) + + apiObjects = append(apiObjects, apiObject) } return apiObjects } +func flattenAccessEndpoint(apiObject *appstream.AccessEndpoint) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["endpoint_type"] = aws.StringValue(apiObject.EndpointType) + tfMap["vpce_id"] = aws.StringValue(apiObject.VpceId) + + return tfMap +} + func flattenAccessEndpoints(apiObjects []*appstream.AccessEndpoint) []map[string]interface{} { - if apiObjects == nil { + if len(apiObjects) == 0 { return nil } var tfList []map[string]interface{} - for _, endpoint := range apiObjects { - tfList = append(tfList, map[string]interface{}{ - "endpoint_type": aws.StringValue(endpoint.EndpointType), - "vpce_id": aws.StringValue(endpoint.VpceId), - }) + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenAccessEndpoint(apiObject)) } return tfList } -func expandApplicationSettings(tfList []interface{}) *appstream.ApplicationSettings { - if len(tfList) == 0 { +func expandApplicationSetting(tfMap map[string]interface{}) *appstream.ApplicationSettings { + if tfMap == nil { return nil } apiObject := &appstream.ApplicationSettings{} - attr := tfList[0].(map[string]interface{}) - if v, ok := attr["enabled"]; ok { + if v, ok := tfMap["enabled"]; ok { apiObject.Enabled = aws.Bool(v.(bool)) } - if v, ok := attr["settings_group"]; ok { + if v, ok := tfMap["settings_group"]; ok { apiObject.SettingsGroup = aws.String(v.(string)) } return apiObject } +func expandApplicationSettings(tfList []interface{}) *appstream.ApplicationSettings { + if len(tfList) == 0 { + return nil + } + + var apiObject *appstream.ApplicationSettings + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject = expandApplicationSetting(tfMap) + } + + return apiObject +} + +func flattenApplicationSetting(apiObject *appstream.ApplicationSettingsResponse) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["enabled"] = aws.BoolValue(apiObject.Enabled) + tfMap["settings_group"] = aws.StringValue(apiObject.SettingsGroup) + + return tfMap +} + func flattenApplicationSettings(apiObject *appstream.ApplicationSettingsResponse) []interface{} { if apiObject == nil { return nil } - tfList := map[string]interface{}{} - tfList["enabled"] = aws.BoolValue(apiObject.Enabled) - tfList["settings_group"] = aws.StringValue(apiObject.SettingsGroup) + var tfList []interface{} + + tfList = append(tfList, flattenApplicationSetting(apiObject)) + + return tfList +} + +func expandStorageConnector(tfMap map[string]interface{}) *appstream.StorageConnector { + if tfMap == nil { + return nil + } + + apiObject := &appstream.StorageConnector{ + ConnectorType: aws.String(tfMap["connector_type"].(string)), + } + if v, ok := tfMap["domains"]; ok && len(v.([]interface{})) > 0 { + apiObject.Domains = expandStringList(v.([]interface{})) + } + if v, ok := tfMap["resource_identifier"]; ok && v.(string) != "" { + apiObject.ResourceIdentifier = aws.String(v.(string)) + } - return []interface{}{tfList} + return apiObject } func expandStorageConnectors(tfList []interface{}) []*appstream.StorageConnector { @@ -479,43 +554,69 @@ func expandStorageConnectors(tfList []interface{}) []*appstream.StorageConnector var apiObjects []*appstream.StorageConnector - for _, v := range tfList { - v1 := v.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) - connector := &appstream.StorageConnector{ - ConnectorType: aws.String(v1["connector_type"].(string)), - } - if v2, ok := v1["domains"]; ok && len(v2.([]interface{})) > 0 { - connector.Domains = expandStringList(v2.([]interface{})) + if !ok { + continue } - if v2, ok := v1["resource_identifier"]; ok && v2.(string) != "" { - connector.ResourceIdentifier = aws.String(v2.(string)) + + apiObject := expandStorageConnector(tfMap) + + if apiObject == nil { + continue } - apiObjects = append(apiObjects, connector) + apiObjects = append(apiObjects, apiObject) } return apiObjects } +func flattenStorageConnector(apiObject *appstream.StorageConnector) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["connector_type"] = aws.StringValue(apiObject.ConnectorType) + tfMap["domains"] = aws.StringValueSlice(apiObject.Domains) + tfMap["resource_identifier"] = aws.StringValue(apiObject.ResourceIdentifier) + + return tfMap +} + func flattenStorageConnectors(apiObjects []*appstream.StorageConnector) []map[string]interface{} { - if apiObjects == nil { + if len(apiObjects) == 0 { return nil } var tfList []map[string]interface{} - for _, connector := range apiObjects { - tfList = append(tfList, map[string]interface{}{ - "connector_type": aws.StringValue(connector.ConnectorType), - "domains": aws.StringValueSlice(connector.Domains), - "resource_identifier": aws.StringValue(connector.ResourceIdentifier), - }) + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenStorageConnector(apiObject)) } return tfList } +func expandUserSetting(tfMap map[string]interface{}) *appstream.UserSetting { + if tfMap == nil { + return nil + } + + apiObject := &appstream.UserSetting{ + Action: aws.String(tfMap["action"].(string)), + Permission: aws.String(tfMap["permission"].(string)), + } + + return apiObject +} + func expandUserSettings(tfList []interface{}) []*appstream.UserSetting { if len(tfList) == 0 { return nil @@ -523,32 +624,49 @@ func expandUserSettings(tfList []interface{}) []*appstream.UserSetting { var apiObjects []*appstream.UserSetting - for _, v := range tfList { - v1 := v.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandUserSetting(tfMap) - user := &appstream.UserSetting{ - Action: aws.String(v1["action"].(string)), - Permission: aws.String(v1["permission"].(string)), + if apiObject == nil { + continue } - apiObjects = append(apiObjects, user) + apiObjects = append(apiObjects, apiObject) } return apiObjects } +func flattenUserSetting(apiObject *appstream.UserSetting) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["action"] = aws.StringValue(apiObject.Action) + tfMap["permission"] = aws.StringValue(apiObject.Permission) + + return tfMap +} + func flattenUserSettings(apiObjects []*appstream.UserSetting) []map[string]interface{} { - if apiObjects == nil { + if len(apiObjects) == 0 { return nil } var tfList []map[string]interface{} - for _, user := range apiObjects { - tfList = append(tfList, map[string]interface{}{ - "action": aws.StringValue(user.Action), - "permission": aws.StringValue(user.Permission), - }) + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + tfList = append(tfList, flattenUserSetting(apiObject)) } return tfList @@ -568,3 +686,20 @@ func suppressAppsStreamStackUserSettings(k, old, new string, d *schema.ResourceD return flagDiffUserSettings } + +func accessEndpointsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(m["endpoint_type"].(string)) + buf.WriteString(m["vpce_id"].(string)) + return hashcode.String(buf.String()) +} + +func storageConnectorsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(m["connector_type"].(string)) + buf.WriteString(fmt.Sprintf("%+v", m["domains"].([]interface{}))) + buf.WriteString(m["resource_identifier"].(string)) + return hashcode.String(buf.String()) +} diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go index b14cbeee008..60735def253 100644 --- a/aws/resource_aws_appstream_stack_test.go +++ b/aws/resource_aws_appstream_stack_test.go @@ -7,14 +7,15 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/appstream" "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" ) func TestAccAwsAppStreamStack_basic(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -23,67 +24,11 @@ func TestAccAwsAppStreamStack_basic(t *testing.T) { ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsAppStreamStackConfigNameGenerated(), + Config: testAccAwsAppStreamStackConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + resource.TestCheckResourceAttr(resourceName, "name", rName), testAccCheckResourceAttrRfc3339(resourceName, "created_time"), - naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccAwsAppStreamStack_nameGenerated(t *testing.T) { - var stackOutput appstream.Stack - resourceName := "aws_appstream_stack.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckAwsAppStreamStackDestroy, - ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), - Steps: []resource.TestStep{ - { - Config: testAccAwsAppStreamStackConfigNameGenerated(), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), - naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccAwsAppStreamStack_namePrefix(t *testing.T) { - var stackOutput appstream.Stack - resourceName := "aws_appstream_stack.test" - namePrefix := "tf-acc-test-prefix-" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testAccCheckAwsAppStreamStackDestroy, - ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), - Steps: []resource.TestStep{ - { - Config: testAccAwsAppStreamStackConfigNamePrefix(namePrefix), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), - naming.TestCheckResourceAttrNameFromPrefix(resourceName, "name", namePrefix), - resource.TestCheckResourceAttr(resourceName, "name_prefix", namePrefix), ), }, { @@ -98,6 +43,7 @@ func TestAccAwsAppStreamStack_namePrefix(t *testing.T) { func TestAccAwsAppStreamStack_disappears(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -106,7 +52,7 @@ func TestAccAwsAppStreamStack_disappears(t *testing.T) { ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsAppStreamStackConfigNameGenerated(), + Config: testAccAwsAppStreamStackConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), testAccCheckResourceDisappears(testAccProvider, resourceAwsAppStreamStack(), resourceName), @@ -120,6 +66,7 @@ func TestAccAwsAppStreamStack_disappears(t *testing.T) { func TestAccAwsAppStreamStack_complete(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" + rName := acctest.RandomWithPrefix("tf-acc-test") description := "Description of a test" descriptionUpdated := "Updated Description of a test" @@ -130,22 +77,20 @@ func TestAccAwsAppStreamStack_complete(t *testing.T) { ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsAppStreamStackConfigComplete(description), + Config: testAccAwsAppStreamStackConfigComplete(rName, description), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + resource.TestCheckResourceAttr(resourceName, "name", rName), testAccCheckResourceAttrRfc3339(resourceName, "created_time"), - naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), resource.TestCheckResourceAttr(resourceName, "description", description), ), }, { - Config: testAccAwsAppStreamStackConfigComplete(descriptionUpdated), + Config: testAccAwsAppStreamStackConfigComplete(rName, descriptionUpdated), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + resource.TestCheckResourceAttr(resourceName, "name", rName), testAccCheckResourceAttrRfc3339(resourceName, "created_time"), - naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), resource.TestCheckResourceAttr(resourceName, "description", descriptionUpdated), ), }, @@ -161,6 +106,7 @@ func TestAccAwsAppStreamStack_complete(t *testing.T) { func TestAccAwsAppStreamStack_withTags(t *testing.T) { var stackOutput appstream.Stack resourceName := "aws_appstream_stack.test" + rName := acctest.RandomWithPrefix("tf-acc-test") description := "Description of a test" descriptionUpdated := "Updated Description of a test" @@ -171,22 +117,20 @@ func TestAccAwsAppStreamStack_withTags(t *testing.T) { ErrorCheck: testAccErrorCheck(t, appstream.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsAppStreamStackConfigComplete(description), + Config: testAccAwsAppStreamStackConfigComplete(rName, description), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + resource.TestCheckResourceAttr(resourceName, "name", rName), testAccCheckResourceAttrRfc3339(resourceName, "created_time"), - naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), resource.TestCheckResourceAttr(resourceName, "description", description), ), }, { - Config: testAccAwsAppStreamStackConfigWithTags(descriptionUpdated), + Config: testAccAwsAppStreamStackConfigWithTags(rName, descriptionUpdated), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppStreamStackExists(resourceName, &stackOutput), + resource.TestCheckResourceAttr(resourceName, "name", rName), testAccCheckResourceAttrRfc3339(resourceName, "created_time"), - naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), resource.TestCheckResourceAttr(resourceName, "description", descriptionUpdated), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), @@ -254,24 +198,19 @@ func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { } -func testAccAwsAppStreamStackConfigNameGenerated() string { - return ` -resource "aws_appstream_stack" "test" {} -` -} - -func testAccAwsAppStreamStackConfigNamePrefix(stackName string) string { +func testAccAwsAppStreamStackConfig(name string) string { return fmt.Sprintf(` resource "aws_appstream_stack" "test" { - name_prefix = %[1]q + name = %[1]q } -`, stackName) +`, name) } -func testAccAwsAppStreamStackConfigComplete(description string) string { +func testAccAwsAppStreamStackConfigComplete(name, description string) string { return fmt.Sprintf(` resource "aws_appstream_stack" "test" { - description = %[1]q + name = %[1]q + description = %[2]q storage_connectors { connector_type = "HOMEFOLDERS" } @@ -296,13 +235,14 @@ resource "aws_appstream_stack" "test" { settings_group = "SettingsGroup" } } -`, description) +`, name, description) } -func testAccAwsAppStreamStackConfigWithTags(description string) string { +func testAccAwsAppStreamStackConfigWithTags(name, description string) string { return fmt.Sprintf(` resource "aws_appstream_stack" "test" { - description = %[1]q + name = %[1]q + description = %[2]q storage_connectors { connector_type = "HOMEFOLDERS" } @@ -330,10 +270,6 @@ resource "aws_appstream_stack" "test" { action = "DOMAIN_PASSWORD_SIGNIN" permission = "ENABLED" } - user_settings { - action = "DOMAIN_SMART_CARD_SIGNIN" - permission = "ENABLED" - } application_settings { enabled = true settings_group = "SettingsGroup" @@ -342,5 +278,5 @@ resource "aws_appstream_stack" "test" { Key = "value" } } -`, description) +`, name, description) } diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index 34658831f32..87a9814f213 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -13,7 +13,7 @@ Provides an AppStream stack. ## Example Usage ```terraform -resource "aws_appstream_stack" "appstream_stack" { +resource "aws_appstream_stack" "example" { name = "stack name" description = "stack description" display_name = "stack display name" @@ -54,10 +54,12 @@ resource "aws_appstream_stack" "appstream_stack" { ## Argument Reference +The following arguments are required: + +* `name` - (Required) Unique name for the AppStream stack. + The following arguments are optional: -* `name` - (Optional) Unique name for the AppStream stack. -* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`. * `description` - (Optional) Description for the AppStream stack. * `display_name` - (Optional) Stack name to display. * `embed_host_domains` - (Optional) Domains where AppStream 2.0 streaming sessions can be embedded in an iframe. You must approve the domains that you want to host embedded AppStream 2.0 streaming sessions. From 4dd409767496c16f9d8bfb2ebcf56c1b7676ff93 Mon Sep 17 00:00:00 2001 From: Edgar Lopez Date: Wed, 18 Aug 2021 11:10:00 -0600 Subject: [PATCH 18/20] added usersetting hash --- aws/resource_aws_appstream_stack.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aws/resource_aws_appstream_stack.go b/aws/resource_aws_appstream_stack.go index 770aa8a2854..88f3fc2ecfd 100644 --- a/aws/resource_aws_appstream_stack.go +++ b/aws/resource_aws_appstream_stack.go @@ -175,6 +175,7 @@ func resourceAwsAppStreamStack() *schema.Resource { }, }, }, + Set: userSettingsHash, }, "tags": tagsSchemaForceNew(), "tags_all": tagsSchemaComputed(), @@ -703,3 +704,11 @@ func storageConnectorsHash(v interface{}) int { buf.WriteString(m["resource_identifier"].(string)) return hashcode.String(buf.String()) } + +func userSettingsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(m["action"].(string)) + buf.WriteString(m["permission"].(string)) + return hashcode.String(buf.String()) +} From 331aa91599d74fb91416ed96a58c306ab84320e3 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 24 Aug 2021 13:32:13 -0400 Subject: [PATCH 19/20] r/appstream_stack: Minor fixes --- aws/resource_aws_appstream_stack_test.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_appstream_stack_test.go b/aws/resource_aws_appstream_stack_test.go index 60735def253..51e16ae93eb 100644 --- a/aws/resource_aws_appstream_stack_test.go +++ b/aws/resource_aws_appstream_stack_test.go @@ -158,7 +158,7 @@ func testAccCheckAwsAppStreamStackExists(resourceName string, appStreamStack *ap resp, err := conn.DescribeStacks(&appstream.DescribeStacksInput{Names: []*string{aws.String(rs.Primary.ID)}}) if err != nil { - return err + return fmt.Errorf("problem checking for AppStream Stack existence: %w", err) } if resp == nil && len(resp.Stacks) == 0 { @@ -186,7 +186,7 @@ func testAccCheckAwsAppStreamStackDestroy(s *terraform.State) error { } if err != nil { - return err + return fmt.Errorf("problem while checking AppStream Stack was destroyed: %w", err) } if resp != nil && len(resp.Stacks) > 0 { @@ -211,25 +211,31 @@ func testAccAwsAppStreamStackConfigComplete(name, description string) string { resource "aws_appstream_stack" "test" { name = %[1]q description = %[2]q + storage_connectors { connector_type = "HOMEFOLDERS" } + user_settings { action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" permission = "ENABLED" } + user_settings { action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" permission = "ENABLED" } + user_settings { action = "FILE_UPLOAD" permission = "ENABLED" } + user_settings { action = "FILE_DOWNLOAD" permission = "ENABLED" } + application_settings { enabled = true settings_group = "SettingsGroup" @@ -243,37 +249,46 @@ func testAccAwsAppStreamStackConfigWithTags(name, description string) string { resource "aws_appstream_stack" "test" { name = %[1]q description = %[2]q + storage_connectors { connector_type = "HOMEFOLDERS" } + user_settings { action = "CLIPBOARD_COPY_FROM_LOCAL_DEVICE" permission = "ENABLED" } + user_settings { action = "CLIPBOARD_COPY_TO_LOCAL_DEVICE" permission = "ENABLED" } + user_settings { action = "FILE_UPLOAD" permission = "DISABLED" } + user_settings { action = "FILE_DOWNLOAD" permission = "ENABLED" } + user_settings { action = "PRINTING_TO_LOCAL_DEVICE" permission = "ENABLED" } + user_settings { action = "DOMAIN_PASSWORD_SIGNIN" permission = "ENABLED" } + application_settings { enabled = true settings_group = "SettingsGroup" } + tags = { Key = "value" } From b18fa1416983b92d87a74e1c58b0af10aa239162 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 24 Aug 2021 13:32:36 -0400 Subject: [PATCH 20/20] docs/r/appstream_stack: Minor fixes --- website/docs/r/appstream_stack.html.markdown | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/website/docs/r/appstream_stack.html.markdown b/website/docs/r/appstream_stack.html.markdown index 87a9814f213..1a0f405be19 100644 --- a/website/docs/r/appstream_stack.html.markdown +++ b/website/docs/r/appstream_stack.html.markdown @@ -60,34 +60,33 @@ The following arguments are required: The following arguments are optional: +* `application_settings` - (Optional) Settings for application settings persistence. * `description` - (Optional) Description for the AppStream stack. * `display_name` - (Optional) Stack name to display. * `embed_host_domains` - (Optional) Domains where AppStream 2.0 streaming sessions can be embedded in an iframe. You must approve the domains that you want to host embedded AppStream 2.0 streaming sessions. -* `redirect_url` - (Optional) URL that users are redirected to after their streaming session ends. * `feedback_url` - (Optional) URL that users are redirected to after they click the Send Feedback link. If no URL is specified, no Send Feedback link is displayed. . +* `redirect_url` - (Optional) URL that users are redirected to after their streaming session ends. * `storage_connectors` - (Optional) Configuration block for the storage connectors to enable. See below. * `user_settings` - (Optional) Configuration block for the actions that are enabled or disabled for users during their streaming sessions. By default, these actions are enabled. See below. -* `application_settings` - (Optional) settings for application settings persistence. ### `storage_connectors` -* `connector_type` - (Required) Type of storage connector. Valid values are: `HOMEFOLDERS`, `GOOGLE_DRIVE`, `ONE_DRIVE` +* `connector_type` - (Required) Type of storage connector. Valid values are: `HOMEFOLDERS`, `GOOGLE_DRIVE`, `ONE_DRIVE`. * `domains` - (Optional) Names of the domains for the account. -* `resource_identifier` - (Optional) The ARN of the storage connector. +* `resource_identifier` - (Optional) ARN of the storage connector. ### `user_settings` -* `action` - (Required) Action that is enabled or disabled. Valid values are: `CLIPBOARD_COPY_FROM_LOCAL_DEVICE`, `CLIPBOARD_COPY_TO_LOCAL_DEVICE`, `FILE_UPLOAD`,`FILE_DOWNLOAD`,`PRINTING_TO_LOCAL_DEVICE`,`DOMAIN_PASSWORD_SIGNIN`,`DOMAIN_SMART_CARD_SIGNIN`, -* `permission` - (Required) Indicates whether the action is enabled or disabled. Valid values are: `ENABLED` , `DISABLED` - +* `action` - (Required) Action that is enabled or disabled. Valid values are: `CLIPBOARD_COPY_FROM_LOCAL_DEVICE`, `CLIPBOARD_COPY_TO_LOCAL_DEVICE`, `FILE_UPLOAD`, `FILE_DOWNLOAD`, `PRINTING_TO_LOCAL_DEVICE`, `DOMAIN_PASSWORD_SIGNIN`, `DOMAIN_SMART_CARD_SIGNIN`. +* `permission` - (Required) Indicates whether the action is enabled or disabled. Valid values are: `ENABLED`, `DISABLED`. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `id` - Unique identifier (ID) of the appstream stack. * `arn` - ARN of the appstream stack. * `created_time` - Date and time, in UTC and extended RFC 3339 format, when the stack was created. +* `id` - Unique ID of the appstream stack. ## Import @@ -95,5 +94,5 @@ In addition to all arguments above, the following attributes are exported: `aws_appstream_stack` can be imported using the id, e.g. ``` -$ terraform import aws_appstream_stack.example stackNameExample +$ terraform import aws_appstream_stack.example stackID ```