diff --git a/.changelog/16108.txt b/.changelog/16108.txt new file mode 100644 index 000000000000..8585c63642c7 --- /dev/null +++ b/.changelog/16108.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_mq_broker: Add RabbitMQ as option for `engine_type`, and new arguments `authentication_strategy`, `ldap_server_metadata`, and `storage_type`. Improve handling of eventual consistency. +``` \ No newline at end of file diff --git a/aws/data_source_aws_mq_broker.go b/aws/data_source_aws_mq_broker.go index 15e314038281..5edfd9159439 100644 --- a/aws/data_source_aws_mq_broker.go +++ b/aws/data_source_aws_mq_broker.go @@ -13,6 +13,18 @@ func dataSourceAwsMqBroker() *schema.Resource { Read: dataSourceAwsmQBrokerRead, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "authentication_strategy": { + Type: schema.TypeString, + Computed: true, + }, + "auto_minor_version_upgrade": { + Type: schema.TypeBool, + Computed: true, + }, "broker_id": { Type: schema.TypeString, Optional: true, @@ -25,14 +37,6 @@ func dataSourceAwsMqBroker() *schema.Resource { Computed: true, ConflictsWith: []string{"broker_id"}, }, - "auto_minor_version_upgrade": { - Type: schema.TypeBool, - Computed: true, - }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, "configuration": { Type: schema.TypeList, Computed: true, @@ -90,15 +94,68 @@ func dataSourceAwsMqBroker() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "endpoints": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "ip_address": { Type: schema.TypeString, Computed: true, }, - "endpoints": { + }, + }, + }, + "ldap_server_metadata": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hosts": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "role_base": { + Type: schema.TypeString, + Computed: true, + }, + "role_name": { + Type: schema.TypeString, + Computed: true, + }, + "role_search_matching": { + Type: schema.TypeString, + Computed: true, + }, + "role_search_subtree": { + Type: schema.TypeBool, + Computed: true, + }, + "service_account_password": { + Type: schema.TypeString, + Computed: true, + }, + "service_account_username": { + Type: schema.TypeString, + Computed: true, + }, + "user_base": { + Type: schema.TypeString, + Computed: true, + }, + "user_role_name": { + Type: schema.TypeString, + Computed: true, + }, + "user_search_matching": { + Type: schema.TypeString, + Computed: true, + }, + "user_search_subtree": { + Type: schema.TypeBool, + Computed: true, + }, }, }, }, @@ -155,6 +212,10 @@ func dataSourceAwsMqBroker() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, }, + "storage_type": { + Type: schema.TypeString, + Computed: true, + }, "subnet_ids": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, diff --git a/aws/data_source_aws_mq_broker_test.go b/aws/data_source_aws_mq_broker_test.go index 523ebc36829d..051225180ad4 100644 --- a/aws/data_source_aws_mq_broker_test.go +++ b/aws/data_source_aws_mq_broker_test.go @@ -27,6 +27,7 @@ func TestAccDataSourceAWSMqBroker_basic(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceByIdName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "broker_name", resourceName, "broker_name"), + resource.TestCheckResourceAttrPair(dataSourceByIdName, "authentication_strategy", resourceName, "authentication_strategy"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "auto_minor_version_upgrade", resourceName, "auto_minor_version_upgrade"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "deployment_mode", resourceName, "deployment_mode"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "configuration.#", resourceName, "configuration.#"), @@ -40,6 +41,7 @@ func TestAccDataSourceAWSMqBroker_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceByIdName, "maintenance_window_start_time.#", resourceName, "maintenance_window_start_time.#"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "publicly_accessible", resourceName, "publicly_accessible"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "security_groups.#", resourceName, "security_groups.#"), + resource.TestCheckResourceAttrPair(dataSourceByIdName, "storage_type", resourceName, "storage_type"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "subnet_ids.#", resourceName, "subnet_ids.#"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttrPair(dataSourceByIdName, "user.#", resourceName, "user.#"), diff --git a/aws/internal/service/mq/waiter/status.go b/aws/internal/service/mq/waiter/status.go new file mode 100644 index 000000000000..a7c34267fe9d --- /dev/null +++ b/aws/internal/service/mq/waiter/status.go @@ -0,0 +1,30 @@ +package waiter + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/mq" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func BrokerStatus(conn *mq.MQ, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := conn.DescribeBroker(&mq.DescribeBrokerInput{ + BrokerId: aws.String(id), + }) + + if tfawserr.ErrCodeEquals(err, mq.ErrCodeNotFoundException) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + if output == nil { + return nil, "", nil + } + + return output, aws.StringValue(output.BrokerState), nil + } +} diff --git a/aws/internal/service/mq/waiter/waiter.go b/aws/internal/service/mq/waiter/waiter.go new file mode 100644 index 000000000000..3bc226dc58df --- /dev/null +++ b/aws/internal/service/mq/waiter/waiter.go @@ -0,0 +1,71 @@ +package waiter + +import ( + "time" + + "github.com/aws/aws-sdk-go/service/mq" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const ( + BrokerCreateTimeout = 30 * time.Minute + BrokerDeleteTimeout = 30 * time.Minute + BrokerRebootTimeout = 30 * time.Minute +) + +func BrokerCreated(conn *mq.MQ, id string) (*mq.DescribeBrokerResponse, error) { + stateConf := resource.StateChangeConf{ + Pending: []string{ + mq.BrokerStateCreationInProgress, + mq.BrokerStateRebootInProgress, + }, + Target: []string{mq.BrokerStateRunning}, + Timeout: BrokerCreateTimeout, + Refresh: BrokerStatus(conn, id), + } + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*mq.DescribeBrokerResponse); ok { + return output, err + } + + return nil, err +} + +func BrokerDeleted(conn *mq.MQ, id string) (*mq.DescribeBrokerResponse, error) { + stateConf := resource.StateChangeConf{ + Pending: []string{ + mq.BrokerStateRunning, + mq.BrokerStateRebootInProgress, + mq.BrokerStateDeletionInProgress, + }, + Target: []string{}, + Timeout: BrokerDeleteTimeout, + Refresh: BrokerStatus(conn, id), + } + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*mq.DescribeBrokerResponse); ok { + return output, err + } + + return nil, err +} + +func BrokerRebooted(conn *mq.MQ, id string) (*mq.DescribeBrokerResponse, error) { + stateConf := resource.StateChangeConf{ + Pending: []string{ + mq.BrokerStateRebootInProgress, + }, + Target: []string{mq.BrokerStateRunning}, + Timeout: BrokerRebootTimeout, + Refresh: BrokerStatus(conn, id), + } + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*mq.DescribeBrokerResponse); ok { + return output, err + } + + return nil, err +} diff --git a/aws/resource_aws_mq_broker.go b/aws/resource_aws_mq_broker.go index 4a4d3938a37b..623b6dd1e6fa 100644 --- a/aws/resource_aws_mq_broker.go +++ b/aws/resource_aws_mq_broker.go @@ -5,16 +5,18 @@ import ( "fmt" "log" "reflect" - "time" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/mq" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "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/mitchellh/copystructure" "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/service/mq/waiter" ) func resourceAwsMqBroker() *schema.Resource { @@ -33,6 +35,16 @@ func resourceAwsMqBroker() *schema.Resource { Optional: true, Default: false, }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "authentication_strategy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(mq.AuthenticationStrategy_Values(), true), + }, "auto_minor_version_upgrade": { Type: schema.TypeBool, Optional: true, @@ -65,14 +77,11 @@ func resourceAwsMqBroker() *schema.Resource { }, }, "deployment_mode": { - Type: schema.TypeString, - Optional: true, - Default: mq.DeploymentModeSingleInstance, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - mq.DeploymentModeSingleInstance, - mq.DeploymentModeActiveStandbyMultiAz, - }, true), + Type: schema.TypeString, + Optional: true, + Default: mq.DeploymentModeSingleInstance, + ForceNew: true, + ValidateFunc: validation.StringInSlice(mq.DeploymentMode_Values(), true), }, "encryption_options": { Type: schema.TypeList, @@ -99,12 +108,10 @@ func resourceAwsMqBroker() *schema.Resource { }, }, "engine_type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - mq.EngineTypeActivemq, - }, true), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(mq.EngineType_Values(), true), }, "engine_version": { Type: schema.TypeString, @@ -116,6 +123,81 @@ func resourceAwsMqBroker() *schema.Resource { Required: true, ForceNew: true, }, + "instances": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "console_url": { + Type: schema.TypeString, + Computed: true, + }, + "endpoints": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ip_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "ldap_server_metadata": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hosts": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "role_base": { + Type: schema.TypeString, + Optional: true, + }, + "role_name": { + Type: schema.TypeString, + Optional: true, + }, + "role_search_matching": { + Type: schema.TypeString, + Optional: true, + }, + "role_search_subtree": { + Type: schema.TypeBool, + Optional: true, + }, + "service_account_password": { + Type: schema.TypeString, + Optional: true, + }, + "service_account_username": { + Type: schema.TypeString, + Optional: true, + }, + "user_base": { + Type: schema.TypeString, + Optional: true, + }, + "user_role_name": { + Type: schema.TypeString, + Optional: true, + }, + "user_search_matching": { + Type: schema.TypeString, + Optional: true, + }, + "user_search_subtree": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, "logs": { Type: schema.TypeList, Optional: true, @@ -132,12 +214,10 @@ func resourceAwsMqBroker() *schema.Resource { "general": { Type: schema.TypeBool, Optional: true, - Default: false, }, "audit": { Type: schema.TypeBool, Optional: true, - Default: false, }, }, }, @@ -151,17 +231,9 @@ func resourceAwsMqBroker() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "day_of_week": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - mq.DayOfWeekSunday, - mq.DayOfWeekMonday, - mq.DayOfWeekTuesday, - mq.DayOfWeekWednesday, - mq.DayOfWeekThursday, - mq.DayOfWeekFriday, - mq.DayOfWeekSaturday, - }, true), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(mq.DayOfWeek_Values(), true), }, "time_of_day": { Type: schema.TypeString, @@ -183,7 +255,13 @@ func resourceAwsMqBroker() *schema.Resource { "security_groups": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, - Required: true, + Optional: true, + }, + "storage_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(mq.BrokerStorageType_Values(), true), }, "subnet_ids": { Type: schema.TypeSet, @@ -192,10 +270,21 @@ func resourceAwsMqBroker() *schema.Resource { Computed: true, ForceNew: true, }, + "tags": tagsSchema(), "user": { Type: schema.TypeSet, Required: true, Set: resourceAwsMqUserHash, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // AWS currently does not support updating the RabbitMQ users beyond resource creation. + // User list is not returned back after creation. + // Updates to users can only be in the RabbitMQ UI. + if v := d.Get("engine_type").(string); strings.EqualFold(v, mq.EngineTypeRabbitmq) && d.Get("arn").(string) != "" { + return true + } + + return false + }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "console_access": { @@ -227,32 +316,6 @@ func resourceAwsMqBroker() *schema.Resource { }, }, }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "instances": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "console_url": { - Type: schema.TypeString, - Computed: true, - }, - "ip_address": { - Type: schema.TypeString, - Computed: true, - }, - "endpoints": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - "tags": tagsSchema(), }, } } @@ -271,20 +334,33 @@ func resourceAwsMqBrokerCreate(d *schema.ResourceData, meta interface{}) error { EngineVersion: aws.String(d.Get("engine_version").(string)), HostInstanceType: aws.String(d.Get("host_instance_type").(string)), PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), - SecurityGroups: expandStringSet(d.Get("security_groups").(*schema.Set)), Users: expandMqUsers(d.Get("user").(*schema.Set).List()), - Logs: expandMqLogs(d.Get("logs").([]interface{})), } + if v, ok := d.GetOk("authentication_strategy"); ok { + input.AuthenticationStrategy = aws.String(v.(string)) + } if v, ok := d.GetOk("configuration"); ok { input.Configuration = expandMqConfigurationId(v.([]interface{})) } if v, ok := d.GetOk("deployment_mode"); ok { input.DeploymentMode = aws.String(v.(string)) } + if v, ok := d.GetOk("logs"); ok && len(v.([]interface{})) > 0 { + input.Logs = expandMqLogs(d.Get("logs").([]interface{})) + } + if v, ok := d.GetOk("ldap_server_metadata"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.LdapServerMetadata = expandMQLDAPServerMetadata(v.([]interface{})[0].(map[string]interface{})) + } if v, ok := d.GetOk("maintenance_window_start_time"); ok { input.MaintenanceWindowStartTime = expandMqWeeklyStartTime(v.([]interface{})) } + if v, ok := d.GetOk("security_groups"); ok && v.(*schema.Set).Len() > 0 { + input.SecurityGroups = expandStringSet(d.Get("security_groups").(*schema.Set)) + } + if v, ok := d.GetOk("storage_type"); ok { + input.StorageType = aws.String(v.(string)) + } if v, ok := d.GetOk("subnet_ids"); ok { input.SubnetIds = expandStringSet(v.(*schema.Set)) } @@ -301,27 +377,8 @@ func resourceAwsMqBrokerCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(aws.StringValue(out.BrokerId)) d.Set("arn", out.BrokerArn) - stateConf := resource.StateChangeConf{ - Pending: []string{ - mq.BrokerStateCreationInProgress, - mq.BrokerStateRebootInProgress, - }, - Target: []string{mq.BrokerStateRunning}, - Timeout: 30 * time.Minute, - Refresh: func() (interface{}, string, error) { - out, err := conn.DescribeBroker(&mq.DescribeBrokerInput{ - BrokerId: aws.String(d.Id()), - }) - if err != nil { - return 42, "", err - } - - return out, *out.BrokerState, nil - }, - } - _, err = stateConf.WaitForState() - if err != nil { - return err + if _, err := waiter.BrokerCreated(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for MQ Broker (%s) creation: %w", d.Id(), err) } return resourceAwsMqBrokerRead(d, meta) @@ -332,56 +389,80 @@ func resourceAwsMqBrokerRead(d *schema.ResourceData, meta interface{}) error { ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig log.Printf("[INFO] Reading MQ Broker: %s", d.Id()) - out, err := conn.DescribeBroker(&mq.DescribeBrokerInput{ + output, err := conn.DescribeBroker(&mq.DescribeBrokerInput{ BrokerId: aws.String(d.Id()), }) + + if !d.IsNewResource() && (tfawserr.ErrCodeEquals(err, mq.ErrCodeNotFoundException) || tfawserr.ErrCodeEquals(err, mq.ErrCodeForbiddenException)) { + log.Printf("[WARN] MQ broker (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + if err != nil { - if isAWSErr(err, mq.ErrCodeNotFoundException, "") { - log.Printf("[WARN] MQ Broker %q not found, removing from state", d.Id()) - d.SetId("") - return nil - } - // API docs say a 404 can also return a 403 - if isAWSErr(err, mq.ErrCodeForbiddenException, "Forbidden") { - log.Printf("[WARN] MQ Broker %q not found, removing from state", d.Id()) - d.SetId("") - return nil - } - return err + return fmt.Errorf("error reading MQ broker (%s): %w", d.Id(), err) } - d.Set("auto_minor_version_upgrade", out.AutoMinorVersionUpgrade) - d.Set("arn", out.BrokerArn) - d.Set("instances", flattenMqBrokerInstances(out.BrokerInstances)) - d.Set("broker_name", out.BrokerName) - d.Set("deployment_mode", out.DeploymentMode) + if output == nil { + return fmt.Errorf("empty response while reading MQ broker (%s)", d.Id()) + } - if err := d.Set("encryption_options", flattenMqEncryptionOptions(out.EncryptionOptions)); err != nil { - return fmt.Errorf("error setting encryption_options: %s", err) + d.Set("arn", output.BrokerArn) + d.Set("authentication_strategy", output.AuthenticationStrategy) + d.Set("auto_minor_version_upgrade", output.AutoMinorVersionUpgrade) + d.Set("broker_name", output.BrokerName) + d.Set("deployment_mode", output.DeploymentMode) + d.Set("engine_type", output.EngineType) + d.Set("engine_version", output.EngineVersion) + d.Set("host_instance_type", output.HostInstanceType) + d.Set("instances", flattenMqBrokerInstances(output.BrokerInstances)) + d.Set("publicly_accessible", output.PubliclyAccessible) + d.Set("security_groups", aws.StringValueSlice(output.SecurityGroups)) + d.Set("storage_type", output.StorageType) + d.Set("subnet_ids", aws.StringValueSlice(output.SubnetIds)) + + if output.Configurations != nil && output.Configurations.Current != nil { + if err := d.Set("configuration", flattenMqConfigurationId(output.Configurations.Current)); err != nil { + return fmt.Errorf("error setting configuration: %w", err) + } + } else { + d.Set("configuration", nil) } - d.Set("engine_type", out.EngineType) - d.Set("engine_version", out.EngineVersion) - d.Set("host_instance_type", out.HostInstanceType) - d.Set("publicly_accessible", out.PubliclyAccessible) - err = d.Set("maintenance_window_start_time", flattenMqWeeklyStartTime(out.MaintenanceWindowStartTime)) - if err != nil { - return err + if output.EncryptionOptions != nil { + if err := d.Set("encryption_options", flattenMqEncryptionOptions(output.EncryptionOptions)); err != nil { + return fmt.Errorf("error setting encryption_options: %w", err) + } + } else { + d.Set("encryption_options", nil) } - d.Set("security_groups", aws.StringValueSlice(out.SecurityGroups)) - d.Set("subnet_ids", aws.StringValueSlice(out.SubnetIds)) - if err := d.Set("logs", flattenMqLogs(out.Logs)); err != nil { - return fmt.Errorf("error setting logs: %s", err) + if output.LdapServerMetadata != nil { + if err := d.Set("ldap_server_metadata", flattenMQLDAPServerMetadata(output.LdapServerMetadata)); err != nil { + return fmt.Errorf("error setting lda_server_metadata: %w", err) + } + } else { + d.Set("ldap_server_metadata", nil) } - err = d.Set("configuration", flattenMqConfigurationId(out.Configurations.Current)) - if err != nil { - return err + if output.Logs != nil { + if err := d.Set("logs", flattenMqLogs(output.Logs)); err != nil { + return fmt.Errorf("error setting logs: %w", err) + } + } else { + d.Set("logs", nil) + } + + if output.MaintenanceWindowStartTime != nil { + if err := d.Set("maintenance_window_start_time", flattenMqWeeklyStartTime(output.MaintenanceWindowStartTime)); err != nil { + return fmt.Errorf("error setting maintenance_window_start_time: %w", err) + } + } else { + d.Set("maintenance_window_start_time", nil) } - rawUsers := make([]*mq.User, len(out.Users)) - for i, u := range out.Users { + rawUsers := make([]*mq.User, len(output.Users)) + for i, u := range output.Users { uOut, err := conn.DescribeUser(&mq.DescribeUserInput{ BrokerId: aws.String(d.Id()), Username: u.Username, @@ -397,13 +478,11 @@ func resourceAwsMqBrokerRead(d *schema.ResourceData, meta interface{}) error { } } - users := flattenMqUsers(rawUsers, d.Get("user").(*schema.Set).List()) - if err = d.Set("user", users); err != nil { - return err + if err := d.Set("user", flattenMqUsers(rawUsers, d.Get("user").(*schema.Set).List())); err != nil { + return fmt.Errorf("error setting user: %w", err) } - - if err := d.Set("tags", keyvaluetags.MqKeyValueTags(out.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %s", err) + if err := d.Set("tags", keyvaluetags.MqKeyValueTags(output.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) } return nil @@ -414,13 +493,28 @@ func resourceAwsMqBrokerUpdate(d *schema.ResourceData, meta interface{}) error { requiresReboot := false + if d.HasChange("ldap_server_metadata") { + input := &mq.UpdateBrokerRequest{ + BrokerId: aws.String(d.Id()), + LdapServerMetadata: nil, + } + + if v, ok := d.GetOk("ldap_server_metadata"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.LdapServerMetadata = expandMQLDAPServerMetadata(v.([]interface{})[0].(map[string]interface{})) + } + + if _, err := conn.UpdateBroker(input); err != nil { + return fmt.Errorf("error updating MQ Broker (%s) LDAP server metadata: %w", d.Id(), err) + } + } + if d.HasChange("security_groups") { _, err := conn.UpdateBroker(&mq.UpdateBrokerRequest{ BrokerId: aws.String(d.Id()), SecurityGroups: expandStringSet(d.Get("security_groups").(*schema.Set)), }) if err != nil { - return fmt.Errorf("error updating MQ Broker (%s) security groups: %s", d.Id(), err) + return fmt.Errorf("error updating MQ Broker (%s) security groups: %w", d.Id(), err) } } @@ -431,7 +525,7 @@ func resourceAwsMqBrokerUpdate(d *schema.ResourceData, meta interface{}) error { Logs: expandMqLogs(d.Get("logs").([]interface{})), }) if err != nil { - return fmt.Errorf("error updating MQ Broker (%s) configuration: %s", d.Id(), err) + return fmt.Errorf("error updating MQ Broker (%s) configuration: %w", d.Id(), err) } requiresReboot = true } @@ -445,7 +539,7 @@ func resourceAwsMqBrokerUpdate(d *schema.ResourceData, meta interface{}) error { usersUpdated, err = updateAwsMqBrokerUsers(conn, d.Id(), o.(*schema.Set).List(), n.(*schema.Set).List()) if err != nil { - return fmt.Errorf("error updating MQ Broker (%s) user: %s", d.Id(), err) + return fmt.Errorf("error updating MQ Broker (%s) user: %w", d.Id(), err) } if usersUpdated { @@ -458,30 +552,11 @@ func resourceAwsMqBrokerUpdate(d *schema.ResourceData, meta interface{}) error { BrokerId: aws.String(d.Id()), }) if err != nil { - return fmt.Errorf("error rebooting MQ Broker (%s): %s", d.Id(), err) + return fmt.Errorf("error rebooting MQ Broker (%s): %w", d.Id(), err) } - stateConf := resource.StateChangeConf{ - Pending: []string{ - mq.BrokerStateRunning, - mq.BrokerStateRebootInProgress, - }, - Target: []string{mq.BrokerStateRunning}, - Timeout: 30 * time.Minute, - Refresh: func() (interface{}, string, error) { - out, err := conn.DescribeBroker(&mq.DescribeBrokerInput{ - BrokerId: aws.String(d.Id()), - }) - if err != nil { - return 42, "", err - } - - return out, *out.BrokerState, nil - }, - } - _, err = stateConf.WaitForState() - if err != nil { - return err + if _, err := waiter.BrokerRebooted(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for MQ Broker (%s) reboot: %w", d.Id(), err) } } @@ -489,7 +564,7 @@ func resourceAwsMqBrokerUpdate(d *schema.ResourceData, meta interface{}) error { o, n := d.GetChange("tags") if err := keyvaluetags.MqUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating MQ Broker (%s) tags: %s", d.Get("arn").(string), err) + return fmt.Errorf("error updating MQ Broker (%s) tags: %w", d.Get("arn").(string), err) } } @@ -507,7 +582,11 @@ func resourceAwsMqBrokerDelete(d *schema.ResourceData, meta interface{}) error { return err } - return waitForMqBrokerDeletion(conn, d.Id()) + if _, err := waiter.BrokerDeleted(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for MQ Broker (%s) deletion: %w", d.Id(), err) + } + + return nil } func resourceAwsMqUserHash(v interface{}) int { @@ -530,33 +609,6 @@ func resourceAwsMqUserHash(v interface{}) int { return hashcode.String(buf.String()) } -func waitForMqBrokerDeletion(conn *mq.MQ, id string) error { - stateConf := resource.StateChangeConf{ - Pending: []string{ - mq.BrokerStateRunning, - mq.BrokerStateRebootInProgress, - mq.BrokerStateDeletionInProgress, - }, - Target: []string{""}, - Timeout: 30 * time.Minute, - Refresh: func() (interface{}, string, error) { - out, err := conn.DescribeBroker(&mq.DescribeBrokerInput{ - BrokerId: aws.String(id), - }) - if err != nil { - if isAWSErr(err, "NotFoundException", "") { - return 42, "", nil - } - return 42, "", err - } - - return out, *out.BrokerState, nil - }, - } - _, err := stateConf.WaitForState() - return err -} - func updateAwsMqBrokerUsers(conn *mq.MQ, bId string, oldUsers, newUsers []interface{}) (bool, error) { // If there are any user creates/deletes/updates, updatedUsers will be set to true updatedUsers := false @@ -724,3 +776,259 @@ func validateMqBrokerPassword(v interface{}, k string) (ws []string, errors []er } return } + +func expandMqUsers(cfg []interface{}) []*mq.User { + users := make([]*mq.User, len(cfg)) + for i, m := range cfg { + u := m.(map[string]interface{}) + user := mq.User{ + Username: aws.String(u["username"].(string)), + Password: aws.String(u["password"].(string)), + } + if v, ok := u["console_access"]; ok { + user.ConsoleAccess = aws.Bool(v.(bool)) + } + if v, ok := u["groups"]; ok { + user.Groups = expandStringSet(v.(*schema.Set)) + } + users[i] = &user + } + return users +} + +// We use cfgdUsers to get & set the password +func flattenMqUsers(users []*mq.User, cfgUsers []interface{}) *schema.Set { + existingPairs := make(map[string]string) + for _, u := range cfgUsers { + user := u.(map[string]interface{}) + username := user["username"].(string) + existingPairs[username] = user["password"].(string) + } + + out := make([]interface{}, 0) + for _, u := range users { + m := map[string]interface{}{ + "username": *u.Username, + } + password := "" + if p, ok := existingPairs[*u.Username]; ok { + password = p + } + if password != "" { + m["password"] = password + } + if u.ConsoleAccess != nil { + m["console_access"] = *u.ConsoleAccess + } + if len(u.Groups) > 0 { + m["groups"] = flattenStringSet(u.Groups) + } + out = append(out, m) + } + return schema.NewSet(resourceAwsMqUserHash, out) +} + +func expandMqWeeklyStartTime(cfg []interface{}) *mq.WeeklyStartTime { + if len(cfg) < 1 { + return nil + } + + m := cfg[0].(map[string]interface{}) + return &mq.WeeklyStartTime{ + DayOfWeek: aws.String(m["day_of_week"].(string)), + TimeOfDay: aws.String(m["time_of_day"].(string)), + TimeZone: aws.String(m["time_zone"].(string)), + } +} + +func flattenMqWeeklyStartTime(wst *mq.WeeklyStartTime) []interface{} { + if wst == nil { + return []interface{}{} + } + m := make(map[string]interface{}) + if wst.DayOfWeek != nil { + m["day_of_week"] = *wst.DayOfWeek + } + if wst.TimeOfDay != nil { + m["time_of_day"] = *wst.TimeOfDay + } + if wst.TimeZone != nil { + m["time_zone"] = *wst.TimeZone + } + return []interface{}{m} +} + +func expandMqConfigurationId(cfg []interface{}) *mq.ConfigurationId { + if len(cfg) < 1 { + return nil + } + + m := cfg[0].(map[string]interface{}) + out := mq.ConfigurationId{ + Id: aws.String(m["id"].(string)), + } + if v, ok := m["revision"].(int); ok && v > 0 { + out.Revision = aws.Int64(int64(v)) + } + + return &out +} + +func flattenMqConfigurationId(cid *mq.ConfigurationId) []interface{} { + if cid == nil { + return []interface{}{} + } + m := make(map[string]interface{}) + if cid.Id != nil { + m["id"] = *cid.Id + } + if cid.Revision != nil { + m["revision"] = *cid.Revision + } + return []interface{}{m} +} + +func flattenMqBrokerInstances(instances []*mq.BrokerInstance) []interface{} { + if len(instances) == 0 { + return []interface{}{} + } + l := make([]interface{}, len(instances)) + for i, instance := range instances { + m := make(map[string]interface{}) + if instance.ConsoleURL != nil { + m["console_url"] = *instance.ConsoleURL + } + if len(instance.Endpoints) > 0 { + m["endpoints"] = aws.StringValueSlice(instance.Endpoints) + } + if instance.IpAddress != nil { + m["ip_address"] = *instance.IpAddress + } + l[i] = m + } + + return l +} + +func flattenMqLogs(logs *mq.LogsSummary) []interface{} { + if logs == nil { + return []interface{}{} + } + + m := map[string]interface{}{} + + if logs.General != nil { + m["general"] = aws.BoolValue(logs.General) + } + + if logs.Audit != nil { + m["audit"] = aws.BoolValue(logs.Audit) + } + + return []interface{}{m} +} + +func expandMqLogs(l []interface{}) *mq.Logs { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + logs := &mq.Logs{} + + if v, ok := m["general"]; ok { + logs.General = aws.Bool(v.(bool)) + } + + if v, ok := m["audit"]; ok { + logs.Audit = aws.Bool(v.(bool)) + } + + return logs +} + +func flattenMQLDAPServerMetadata(apiObject *mq.LdapServerMetadataOutput) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Hosts; v != nil { + tfMap["hosts"] = aws.StringValueSlice(v) + } + if v := apiObject.RoleBase; v != nil { + tfMap["role_base"] = aws.StringValue(v) + } + if v := apiObject.RoleName; v != nil { + tfMap["role_name"] = aws.StringValue(v) + } + if v := apiObject.RoleSearchMatching; v != nil { + tfMap["role_search_matching"] = aws.StringValue(v) + } + if v := apiObject.RoleSearchSubtree; v != nil { + tfMap["role_search_subtree"] = aws.BoolValue(v) + } + if v := apiObject.ServiceAccountUsername; v != nil { + tfMap["service_account_username"] = aws.StringValue(v) + } + if v := apiObject.UserBase; v != nil { + tfMap["user_base"] = aws.StringValue(v) + } + if v := apiObject.UserRoleName; v != nil { + tfMap["user_role_name"] = aws.StringValue(v) + } + if v := apiObject.UserSearchMatching; v != nil { + tfMap["user_search_matching"] = aws.StringValue(v) + } + if v := apiObject.UserSearchSubtree; v != nil { + tfMap["user_search_subtree"] = aws.BoolValue(v) + } + + return tfMap +} + +func expandMQLDAPServerMetadata(tfMap map[string]interface{}) *mq.LdapServerMetadataInput { + if tfMap == nil { + return nil + } + + apiObject := &mq.LdapServerMetadataInput{} + + if v, ok := tfMap["hosts"]; ok && len(v.([]interface{})) > 0 { + apiObject.Hosts = expandStringList(v.([]interface{})) + } + if v, ok := tfMap["role_base"].(string); ok && v != "" { + apiObject.RoleBase = aws.String(v) + } + if v, ok := tfMap["role_name"].(string); ok && v != "" { + apiObject.RoleName = aws.String(v) + } + if v, ok := tfMap["role_search_matching"].(string); ok && v != "" { + apiObject.RoleSearchMatching = aws.String(v) + } + if v, ok := tfMap["role_search_subtree"].(bool); ok { + apiObject.RoleSearchSubtree = aws.Bool(v) + } + if v, ok := tfMap["service_account_password"].(string); ok && v != "" { + apiObject.ServiceAccountPassword = aws.String(v) + } + if v, ok := tfMap["service_account_username"].(string); ok && v != "" { + apiObject.ServiceAccountUsername = aws.String(v) + } + if v, ok := tfMap["user_base"].(string); ok && v != "" { + apiObject.UserBase = aws.String(v) + } + if v, ok := tfMap["user_role_name"].(string); ok && v != "" { + apiObject.UserRoleName = aws.String(v) + } + if v, ok := tfMap["user_search_matching"].(string); ok && v != "" { + apiObject.UserSearchMatching = aws.String(v) + } + if v, ok := tfMap["user_search_subtree"].(bool); ok { + apiObject.UserSearchSubtree = aws.Bool(v) + } + + return apiObject +} diff --git a/aws/resource_aws_mq_broker_test.go b/aws/resource_aws_mq_broker_test.go index bb4e3a2e6761..a68b006ce94b 100644 --- a/aws/resource_aws_mq_broker_test.go +++ b/aws/resource_aws_mq_broker_test.go @@ -3,16 +3,20 @@ package aws import ( "fmt" "log" + "math/rand" "regexp" "strings" "testing" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/mq" + "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/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/mq/waiter" ) func init() { @@ -94,16 +98,31 @@ func testSweepMqBrokers(region string) error { } log.Printf("[DEBUG] %d MQ brokers found", len(resp.BrokerSummaries)) + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(resp.BrokerSummaries), func(i, j int) { + resp.BrokerSummaries[i], resp.BrokerSummaries[j] = resp.BrokerSummaries[j], resp.BrokerSummaries[i] + }) + for _, bs := range resp.BrokerSummaries { - log.Printf("[INFO] Deleting MQ broker %s", *bs.BrokerId) + log.Printf("[INFO] Deleting MQ broker %s", aws.StringValue(bs.BrokerId)) _, err := conn.DeleteBroker(&mq.DeleteBrokerInput{ BrokerId: bs.BrokerId, }) + if tfawserr.ErrMessageContains(err, mq.ErrCodeBadRequestException, "while in state [CREATION_IN_PROGRESS") { + log.Printf("[WARN] Broker in state CREATION_IN_PROGRESS and must complete creation before deletion") + if _, err = waiter.BrokerCreated(conn, aws.StringValue(bs.BrokerId)); err != nil { + return err + } + + log.Printf("[WARN] Retrying deletion of broker %s", aws.StringValue(bs.BrokerId)) + _, err = conn.DeleteBroker(&mq.DeleteBrokerInput{ + BrokerId: bs.BrokerId, + }) + } if err != nil { return err } - err = waitForMqBrokerDeletion(conn, *bs.BrokerId) - if err != nil { + if _, err = waiter.BrokerDeleted(conn, aws.StringValue(bs.BrokerId)); err != nil { return err } } @@ -249,6 +268,7 @@ func TestAccAWSMqBroker_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAwsMqBrokerExists(resourceName, &broker), resource.TestCheckResourceAttr(resourceName, "auto_minor_version_upgrade", "false"), + resource.TestCheckResourceAttr(resourceName, "authentication_strategy", "simple"), resource.TestCheckResourceAttr(resourceName, "broker_name", brokerName), resource.TestCheckResourceAttr(resourceName, "configuration.#", "1"), resource.TestMatchResourceAttr(resourceName, "configuration.0.id", regexp.MustCompile(`^c-[a-z0-9-]+$`)), @@ -268,6 +288,7 @@ func TestAccAWSMqBroker_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "maintenance_window_start_time.0.time_zone", "UTC"), resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_type", "efs"), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), resource.TestCheckResourceAttr(resourceName, "user.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "user.*", map[string]string{ @@ -815,6 +836,40 @@ func TestAccAWSMqBroker_disappears(t *testing.T) { }) } +func TestAccAWSMqBroker_rabbitmq(t *testing.T) { + var broker mq.DescribeBrokerResponse + sgName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + brokerName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + resourceName := "aws_mq_broker.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSMq(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsMqBrokerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMqBrokerConfigRabbit(sgName, brokerName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMqBrokerExists(resourceName, &broker), + resource.TestCheckResourceAttr(resourceName, "configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "engine_type", "RabbitMQ"), + resource.TestCheckResourceAttr(resourceName, "engine_version", "3.8.6"), + resource.TestCheckResourceAttr(resourceName, "host_instance_type", "mq.t3.micro"), + resource.TestCheckResourceAttr(resourceName, "instances.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instances.0.endpoints.#", "1"), + resource.TestMatchResourceAttr(resourceName, "instances.0.endpoints.0", regexp.MustCompile(`^amqps://[a-z0-9-\.]+:5671$`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + func testAccCheckAwsMqBrokerDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).mqconn @@ -897,15 +952,17 @@ func testAccPreCheckAWSMq(t *testing.T) { func testAccMqBrokerConfig(sgName, brokerName string) string { return fmt.Sprintf(` resource "aws_security_group" "test" { - name = "%s" + name = %[1]q } resource "aws_mq_broker" "test" { - broker_name = "%s" - engine_type = "ActiveMQ" - engine_version = "5.15.0" - host_instance_type = "mq.t2.micro" - security_groups = [aws_security_group.test.id] + broker_name = %[2]q + engine_type = "ActiveMQ" + engine_version = "5.15.0" + host_instance_type = "mq.t2.micro" + security_groups = [aws_security_group.test.id] + authentication_strategy = "simple" + storage_type = "efs" logs { general = true @@ -1369,3 +1426,24 @@ resource "aws_mq_broker" "test" { } `, sgName, sgName, brokerName) } + +func testAccMqBrokerConfigRabbit(sgName, brokerName string) string { + return fmt.Sprintf(` +resource "aws_security_group" "test" { + name = "%s" +} + +resource "aws_mq_broker" "test" { + broker_name = "%s" + engine_type = "RabbitMQ" + engine_version = "3.8.6" + host_instance_type = "mq.t3.micro" + security_groups = [aws_security_group.test.id] + + user { + username = "Test" + password = "TestTest1234" + } +} +`, sgName, brokerName) +} diff --git a/aws/structure.go b/aws/structure.go index 6c715cc82031..dca6f400f6d0 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -35,7 +35,6 @@ import ( "github.com/aws/aws-sdk-go/service/kinesis" "github.com/aws/aws-sdk-go/service/lambda" "github.com/aws/aws-sdk-go/service/macie" - "github.com/aws/aws-sdk-go/service/mq" "github.com/aws/aws-sdk-go/service/neptune" "github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/redshift" @@ -3576,167 +3575,6 @@ func canonicalXML(s string) (string, error) { return results, nil } -func expandMqUsers(cfg []interface{}) []*mq.User { - users := make([]*mq.User, len(cfg)) - for i, m := range cfg { - u := m.(map[string]interface{}) - user := mq.User{ - Username: aws.String(u["username"].(string)), - Password: aws.String(u["password"].(string)), - } - if v, ok := u["console_access"]; ok { - user.ConsoleAccess = aws.Bool(v.(bool)) - } - if v, ok := u["groups"]; ok { - user.Groups = expandStringSet(v.(*schema.Set)) - } - users[i] = &user - } - return users -} - -// We use cfgdUsers to get & set the password -func flattenMqUsers(users []*mq.User, cfgUsers []interface{}) *schema.Set { - existingPairs := make(map[string]string) - for _, u := range cfgUsers { - user := u.(map[string]interface{}) - username := user["username"].(string) - existingPairs[username] = user["password"].(string) - } - - out := make([]interface{}, 0) - for _, u := range users { - m := map[string]interface{}{ - "username": *u.Username, - } - password := "" - if p, ok := existingPairs[*u.Username]; ok { - password = p - } - if password != "" { - m["password"] = password - } - if u.ConsoleAccess != nil { - m["console_access"] = *u.ConsoleAccess - } - if len(u.Groups) > 0 { - m["groups"] = flattenStringSet(u.Groups) - } - out = append(out, m) - } - return schema.NewSet(resourceAwsMqUserHash, out) -} - -func expandMqWeeklyStartTime(cfg []interface{}) *mq.WeeklyStartTime { - if len(cfg) < 1 { - return nil - } - - m := cfg[0].(map[string]interface{}) - return &mq.WeeklyStartTime{ - DayOfWeek: aws.String(m["day_of_week"].(string)), - TimeOfDay: aws.String(m["time_of_day"].(string)), - TimeZone: aws.String(m["time_zone"].(string)), - } -} - -func flattenMqWeeklyStartTime(wst *mq.WeeklyStartTime) []interface{} { - if wst == nil { - return []interface{}{} - } - m := make(map[string]interface{}) - if wst.DayOfWeek != nil { - m["day_of_week"] = *wst.DayOfWeek - } - if wst.TimeOfDay != nil { - m["time_of_day"] = *wst.TimeOfDay - } - if wst.TimeZone != nil { - m["time_zone"] = *wst.TimeZone - } - return []interface{}{m} -} - -func expandMqConfigurationId(cfg []interface{}) *mq.ConfigurationId { - if len(cfg) < 1 { - return nil - } - - m := cfg[0].(map[string]interface{}) - out := mq.ConfigurationId{ - Id: aws.String(m["id"].(string)), - } - if v, ok := m["revision"].(int); ok && v > 0 { - out.Revision = aws.Int64(int64(v)) - } - - return &out -} - -func flattenMqConfigurationId(cid *mq.ConfigurationId) []interface{} { - if cid == nil { - return []interface{}{} - } - m := make(map[string]interface{}) - if cid.Id != nil { - m["id"] = *cid.Id - } - if cid.Revision != nil { - m["revision"] = *cid.Revision - } - return []interface{}{m} -} - -func flattenMqBrokerInstances(instances []*mq.BrokerInstance) []interface{} { - if len(instances) == 0 { - return []interface{}{} - } - l := make([]interface{}, len(instances)) - for i, instance := range instances { - m := make(map[string]interface{}) - if instance.ConsoleURL != nil { - m["console_url"] = *instance.ConsoleURL - } - if len(instance.Endpoints) > 0 { - m["endpoints"] = aws.StringValueSlice(instance.Endpoints) - } - if instance.IpAddress != nil { - m["ip_address"] = *instance.IpAddress - } - l[i] = m - } - - return l -} - -func flattenMqLogs(logs *mq.LogsSummary) []interface{} { - if logs == nil { - return []interface{}{} - } - - m := map[string]interface{}{ - "general": aws.BoolValue(logs.General), - "audit": aws.BoolValue(logs.Audit), - } - - return []interface{}{m} -} - -func expandMqLogs(l []interface{}) *mq.Logs { - if len(l) == 0 || l[0] == nil { - return nil - } - - m := l[0].(map[string]interface{}) - - logs := &mq.Logs{ - Audit: aws.Bool(m["audit"].(bool)), - General: aws.Bool(m["general"].(bool)), - } - - return logs -} - func flattenResourceLifecycleConfig(rlc *elasticbeanstalk.ApplicationResourceLifecycleConfig) []map[string]interface{} { result := make([]map[string]interface{}, 0, 1) diff --git a/website/docs/r/mq_broker.html.markdown b/website/docs/r/mq_broker.html.markdown index a9d16acc33c1..2a54373699b7 100644 --- a/website/docs/r/mq_broker.html.markdown +++ b/website/docs/r/mq_broker.html.markdown @@ -8,22 +8,16 @@ description: |- # Resource: aws_mq_broker -Provides an MQ Broker Resource. This resources also manages users for the broker. +Provides an Amazon MQ broker resource. This resources also manages users for the broker. -For more information on Amazon MQ, see [Amazon MQ documentation](https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/welcome.html). +-> For more information on Amazon MQ, see [Amazon MQ documentation](https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/welcome.html). -Changes to an MQ Broker can occur when you change a -parameter, such as `configuration` or `user`, and are reflected in the next maintenance -window. Because of this, Terraform may report a difference in its planning -phase because a modification has not yet taken place. You can use the -`apply_immediately` flag to instruct the service to apply the change immediately -(see documentation below). +~> **NOTE:** Amazon MQ currently places limits on **RabbitMQ** brokers. For example, a RabbitMQ broker cannot have: instances with an associated IP address of an ENI attached to the broker, an associated LDAP server to authenticate and authorize broker connections, storage type `EFS`, audit logging, or `configuration` blocks. Although this resource allows you to create RabbitMQ users, RabbitMQ users cannot have console access or groups. Also, Amazon MQ does not return information about RabbitMQ users so drift detection is not possible. -~> **Note:** using `apply_immediately` can result in a -brief downtime as the broker reboots. +~> **NOTE:** Changes to an MQ Broker can occur when you change a parameter, such as `configuration` or `user`, and are reflected in the next maintenance window. Because of this, Terraform may report a difference in its planning phase because a modification has not yet taken place. You can use the `apply_immediately` flag to instruct the service to apply the change immediately (see documentation below). Using `apply_immediately` can result in a brief downtime as the broker reboots. + +~> **NOTE:** All arguments including the username and password will be stored in the raw state as plain-text. [Read more about sensitive data in state](https://www.terraform.io/docs/state/sensitive-data.html). -~> **Note:** All arguments including the username and password will be stored in the raw state as plain-text. -[Read more about sensitive data in state](https://www.terraform.io/docs/state/sensitive-data.html). ## Example Usage @@ -50,73 +44,105 @@ resource "aws_mq_broker" "example" { ## Argument Reference -The following arguments are supported: - -* `apply_immediately` - (Optional) Specifies whether any broker modifications - are applied immediately, or during the next maintenance window. Default is `false`. -* `auto_minor_version_upgrade` - (Optional) Enables automatic upgrades to new minor versions for brokers, as Apache releases the versions. -* `broker_name` - (Required) The name of the broker. -* `configuration` - (Optional) Configuration of the broker. See below. -* `deployment_mode` - (Optional) The deployment mode of the broker. Supported: `SINGLE_INSTANCE` and `ACTIVE_STANDBY_MULTI_AZ`. Defaults to `SINGLE_INSTANCE`. -* `encryption_options` - (Optional) Configuration block containing encryption options. See below. -* `engine_type` - (Required) The type of broker engine. Currently, Amazon MQ supports only `ActiveMQ`. -* `engine_version` - (Required) The version of the broker engine. See the [AmazonMQ Broker Engine docs](https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/broker-engine.html) for supported versions. -* `host_instance_type` - (Required) The broker's instance type. e.g. `mq.t2.micro` or `mq.m4.large` +The following arguments are required: + +* `broker_name` - (Required) Name of the broker. +* `engine_type` - (Required) Type of broker engine. Valid values are `ActiveMQ` and `RabbitMQ`. +* `engine_version` - (Required) Version of the broker engine. See the [AmazonMQ Broker Engine docs](https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/broker-engine.html) for supported versions. For example, `5.15.0`. +* `host_instance_type` - (Required) Broker's instance type. For example, `mq.t2.micro`, `mq.m4.large`. +* `user` - (Required) Configuration block for broker users. For `engine_type` of `RabbitMQ`, Amazon MQ does not return broker users preventing this resource from making user updates and drift detection. Detailed below. + +The following arguments are optional: + +* `apply_immediately` - (Optional) Specifies whether any broker modifications are applied immediately, or during the next maintenance window. Default is `false`. +* `authentication_strategy` - (Optional) Authentication strategy used to secure the broker. Valid values are `simple` and `ldap`. `ldap` is not supported for `engine_type` `RabbitMQ`. +* `auto_minor_version_upgrade` - (Optional) Whether to automatically upgrade to new minor versions of brokers as Amazon MQ makes releases available. +* `configuration` - (Optional) Configuration block for broker configuration. Applies to `engine_type` of `ActiveMQ` only. Detailed below. +* `deployment_mode` - (Optional) Deployment mode of the broker. Valid values are `SINGLE_INSTANCE`, `ACTIVE_STANDBY_MULTI_AZ`, and `CLUSTER_MULTI_AZ`. Default is `SINGLE_INSTANCE`. +* `encryption_options` - (Optional) Configuration block containing encryption options. Detailed below. +* `ldap_server_metadata` - (Optional) Configuration block for the LDAP server used to authenticate and authorize connections to the broker. Not supported for `engine_type` `RabbitMQ`. Detailed below. +* `logs` - (Optional) Configuration block for the logging configuration of the broker. Detailed below. +* `maintenance_window_start_time` - (Optional) Configuration block for the maintenance window start time. Detailed below. * `publicly_accessible` - (Optional) Whether to enable connections from applications outside of the VPC that hosts the broker's subnets. -* `security_groups` - (Required) The list of security group IDs assigned to the broker. -* `subnet_ids` - (Optional) The list of subnet IDs in which to launch the broker. A `SINGLE_INSTANCE` deployment requires one subnet. An `ACTIVE_STANDBY_MULTI_AZ` deployment requires two subnets. -* `maintenance_window_start_time` - (Optional) Maintenance window start time. See below. -* `logs` - (Optional) Logging configuration of the broker. See below. -* `user` - (Required) The list of all ActiveMQ usernames for the specified broker. See below. -* `tags` - (Optional) A map of tags to assign to the resource. +* `security_groups` - (Optional) List of security group IDs assigned to the broker. +* `storage_type` - (Optional) Storage type of the broker. For `engine_type` `ActiveMQ`, the valid values are `efs` and `ebs`. For `engine_type` `RabbitMQ`, only `EBS` is supported. +* `subnet_ids` - (Optional) List of subnet IDs in which to launch the broker. A `SINGLE_INSTANCE` deployment requires one subnet. An `ACTIVE_STANDBY_MULTI_AZ` deployment requires multiple subnets. +* `tags` - (Optional) Map of tags to assign to the broker. -### Nested Fields +### configuration -#### `configuration` +The following arguments are optional: * `id` - (Optional) The Configuration ID. * `revision` - (Optional) Revision of the Configuration. -#### `encryption_options` +### encryption_options + +The following arguments are optional: -* `kms_key_id` - (Optional) Amazon Resource Name (ARN) of Key Management Service (KMS) Customer Master Key (CMK) to use for encryption at rest. Requires setting `use_aws_owned_key` to `false`. To perform drift detection when AWS managed CMKs or customer managed CMKs are in use, this value must be configured. -* `use_aws_owned_key` - (Optional) Boolean to enable an AWS owned Key Management Service (KMS) Customer Master Key (CMK) that is not in your account. Defaults to `true`. Setting to `false` without configuring `kms_key_id` will create an AWS managed Customer Master Key (CMK) aliased to `aws/mq` in your account. +* `kms_key_id` - (Optional) Amazon Resource Name (ARN) of Key Management Service (KMS) Customer Master Key (CMK) to use for encryption at rest. Requires setting `use_aws_owned_key` to `false`. To perform drift detection when AWS-managed CMKs or customer-managed CMKs are in use, this value must be configured. +* `use_aws_owned_key` - (Optional) Whether to enable an AWS-owned KMS CMK that is not in your account. Defaults to `true`. Setting to `false` without configuring `kms_key_id` will create an AWS-managed CMK aliased to `aws/mq` in your account. -#### `maintenance_window_start_time` +### ldap_server_metadata -* `day_of_week` - (Required) The day of the week. e.g. `MONDAY`, `TUESDAY`, or `WEDNESDAY` -* `time_of_day` - (Required) The time, in 24-hour format. e.g. `02:00` -* `time_zone` - (Required) The time zone, UTC by default, in either the Country/City format, or the UTC offset format. e.g. `CET` +The following arguments are optional: -~> **NOTE:** AWS currently does not support updating the maintenance window beyond resource creation. +* `hosts` - (Optional) List of a fully qualified domain name of the LDAP server and an optional failover server. +* `role_base` - (Optional) Fully qualified name of the directory to search for a user’s groups. +* `role_name` - (Optional) Specifies the LDAP attribute that identifies the group name attribute in the object returned from the group membership query. +* `role_search_matching` - (Optional) Search criteria for groups. +* `role_search_subtree` - (Optional) Whether the directory search scope is the entire sub-tree. +* `service_account_password` - (Optional) Service account password. +* `service_account_username` - (Optional) Service account username. +* `user_base` - (Optional) Fully qualified name of the directory where you want to search for users. +* `user_role_name` - (Optional) Specifies the name of the LDAP attribute for the user group membership. +* `user_search_matching` - (Optional) Search criteria for users. +* `user_search_subtree` - (Optional) Whether the directory search scope is the entire sub-tree. -### `logs` +### logs +The following arguments are optional: + +* `audit` - (Optional) Enables audit logging. Auditing is only possible for `engine_type` of `ActiveMQ`. User management action made using JMX or the ActiveMQ Web Console is logged. Defaults to `false`. * `general` - (Optional) Enables general logging via CloudWatch. Defaults to `false`. -* `audit` - (Optional) Enables audit logging. User management action made using JMX or the ActiveMQ Web Console is logged. Defaults to `false`. -#### `user` +### maintenance_window_start_time + +The following arguments are required: + +* `day_of_week` - (Required) Day of the week, e.g. `MONDAY`, `TUESDAY`, or `WEDNESDAY`. +* `time_of_day` - (Required) Time, in 24-hour format, e.g. `02:00`. +* `time_zone` - (Required) Time zone in either the Country/City format or the UTC offset format, e.g. `CET`. + +~> **NOTE:** Amazon MQ currently does not support updating the maintenance window. Changes to the maintenance window start time will force a new broker to be created. + +### user + +* `console_access` - (Optional) Whether to enable access to the [ActiveMQ Web Console](http://activemq.apache.org/web-console.html) for the user. Applies to `engine_type` of `ActiveMQ` only. +* `groups` - (Optional) List of groups (20 maximum) to which the ActiveMQ user belongs. Applies to `engine_type` of `ActiveMQ` only. +* `password` - (Required) Password of the user. It must be 12 to 250 characters long, at least 4 unique characters, and must not contain commas. +* `username` - (Required) Username of the user. -* `console_access` - (Optional) Whether to enable access to the [ActiveMQ Web Console](http://activemq.apache.org/web-console.html) for the user. -* `groups` - (Optional) The list of groups (20 maximum) to which the ActiveMQ user belongs. -* `password` - (Required) The password of the user. It must be 12 to 250 characters long, at least 4 unique characters, and must not contain commas. -* `username` - (Required) The username of the user. +~> **NOTE:** AWS currently does not support updating RabbitMQ users. Updates to users can only be in the RabbitMQ UI. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `id` - The unique ID that Amazon MQ generates for the broker. -* `arn` - The ARN of the broker. -* `instances` - A list of information about allocated brokers (both active & standby). +* `arn` - ARN of the broker. +* `id` - Unique ID that Amazon MQ generates for the broker. +* `instances` - List of information about allocated brokers (both active & standby). * `instances.0.console_url` - The URL of the broker's [ActiveMQ Web Console](http://activemq.apache.org/web-console.html). - * `instances.0.ip_address` - The IP Address of the broker. - * `instances.0.endpoints` - The broker's wire-level protocol endpoints in the following order & format referenceable e.g. as `instances.0.endpoints.0` (SSL): - * `ssl://broker-id.mq.us-west-2.amazonaws.com:61617` - * `amqp+ssl://broker-id.mq.us-west-2.amazonaws.com:5671` - * `stomp+ssl://broker-id.mq.us-west-2.amazonaws.com:61614` - * `mqtt+ssl://broker-id.mq.us-west-2.amazonaws.com:8883` - * `wss://broker-id.mq.us-west-2.amazonaws.com:61619` + * `instances.0.ip_address` - IP Address of the broker. + * `instances.0.endpoints` - Broker's wire-level protocol endpoints in the following order & format referenceable e.g. as `instances.0.endpoints.0` (SSL): + * For `ActiveMQ`: + * `ssl://broker-id.mq.us-west-2.amazonaws.com:61617` + * `amqp+ssl://broker-id.mq.us-west-2.amazonaws.com:5671` + * `stomp+ssl://broker-id.mq.us-west-2.amazonaws.com:61614` + * `mqtt+ssl://broker-id.mq.us-west-2.amazonaws.com:8883` + * `wss://broker-id.mq.us-west-2.amazonaws.com:61619` + * For `RabbitMQ`: + * `amqps://broker-id.mq.us-west-2.amazonaws.com:5671` ## Import