diff --git a/.changelog/39931.txt b/.changelog/39931.txt new file mode 100644 index 00000000000..1ff4cc88674 --- /dev/null +++ b/.changelog/39931.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_ssmquicksetup_configuration_manager +``` diff --git a/internal/service/ssmquicksetup/configuration_manager.go b/internal/service/ssmquicksetup/configuration_manager.go new file mode 100644 index 00000000000..34b650949e3 --- /dev/null +++ b/internal/service/ssmquicksetup/configuration_manager.go @@ -0,0 +1,444 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssmquicksetup + +import ( + "context" + "errors" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_ssmquicksetup_configuration_manager", name="Configuration Manager") +// @Tags(identifierAttribute="manager_arn") +func newResourceConfigurationManager(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceConfigurationManager{} + + r.SetDefaultCreateTimeout(20 * time.Minute) + r.SetDefaultUpdateTimeout(20 * time.Minute) + r.SetDefaultDeleteTimeout(20 * time.Minute) + + return r, nil +} + +const ( + ResNameConfigurationManager = "Configuration Manager" +) + +type resourceConfigurationManager struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceConfigurationManager) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_ssmquicksetup_configuration_manager" +} + +func (r *resourceConfigurationManager) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrDescription: schema.StringAttribute{ + Optional: true, + // The API returns an empty string when description is omitted. To prevent "inconsistent + // final plan" errors when null, mark this argument as optional/computed. + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "manager_arn": framework.ARNAttributeComputedOnly(), + names.AttrName: schema.StringAttribute{ + Required: true, + }, + "status_summaries": schema.ListAttribute{ + Computed: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[statusSummaryModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[statusSummaryModel](ctx), + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + Blocks: map[string]schema.Block{ + "configuration_definition": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[configurationDefinitionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrID: schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "local_deployment_administration_role_arn": schema.StringAttribute{ + Optional: true, + }, + "local_deployment_execution_role_name": schema.StringAttribute{ + Optional: true, + }, + names.AttrParameters: schema.MapAttribute{ + CustomType: fwtypes.MapOfStringType, + ElementType: types.StringType, + Required: true, + }, + names.AttrType: schema.StringAttribute{ + Required: true, + }, + "type_version": schema.StringAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + }, + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *resourceConfigurationManager) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().SSMQuickSetupClient(ctx) + + var plan resourceConfigurationManagerModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var input ssmquicksetup.CreateConfigurationManagerInput + resp.Diagnostics.Append(flex.Expand(ctx, plan, &input)...) + if resp.Diagnostics.HasError() { + return + } + input.Tags = getTagsIn(ctx) + + out, err := conn.CreateConfigurationManager(ctx, &input) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionCreating, ResNameConfigurationManager, plan.Name.String(), err), + err.Error(), + ) + return + } + if out == nil || out.ManagerArn == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionCreating, ResNameConfigurationManager, plan.Name.String(), nil), + errors.New("empty output").Error(), + ) + return + } + + plan.ManagerARN = flex.StringToFramework(ctx, out.ManagerArn) + + createTimeout := r.CreateTimeout(ctx, plan.Timeouts) + statusOut, err := waitConfigurationManagerCreated(ctx, conn, plan.ManagerARN.ValueString(), createTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionWaitingForCreation, ResNameConfigurationManager, plan.Name.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, statusOut, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceConfigurationManager) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().SSMQuickSetupClient(ctx) + + var state resourceConfigurationManagerModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findConfigurationManagerByID(ctx, conn, state.ManagerARN.ValueString()) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionSetting, ResNameConfigurationManager, state.ManagerARN.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceConfigurationManager) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().SSMQuickSetupClient(ctx) + + var plan, state resourceConfigurationManagerModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.Name.Equal(state.Name) || + !plan.Description.Equal(state.Description) { + var input ssmquicksetup.UpdateConfigurationManagerInput + resp.Diagnostics.Append(flex.Expand(ctx, plan, &input)...) + if resp.Diagnostics.HasError() { + return + } + + _, err := conn.UpdateConfigurationManager(ctx, &input) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionUpdating, ResNameConfigurationManager, plan.ManagerARN.String(), err), + err.Error(), + ) + return + } + } + + if !plan.ConfigurationDefinition.Equal(state.ConfigurationDefinition) { + var inputs []ssmquicksetup.UpdateConfigurationDefinitionInput + resp.Diagnostics.Append(flex.Expand(ctx, plan.ConfigurationDefinition, &inputs)...) + if resp.Diagnostics.HasError() { + return + } + + for _, input := range inputs { + input.ManagerArn = plan.ManagerARN.ValueStringPointer() + + _, err := conn.UpdateConfigurationDefinition(ctx, &input) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionUpdating, ResNameConfigurationManager, plan.ManagerARN.String(), err), + err.Error(), + ) + return + } + } + } + + updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) + statusOut, err := waitConfigurationManagerUpdated(ctx, conn, plan.ManagerARN.ValueString(), updateTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionWaitingForUpdate, ResNameConfigurationManager, plan.ManagerARN.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, statusOut, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceConfigurationManager) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().SSMQuickSetupClient(ctx) + + var state resourceConfigurationManagerModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + input := ssmquicksetup.DeleteConfigurationManagerInput{ + ManagerArn: state.ManagerARN.ValueStringPointer(), + } + + _, err := conn.DeleteConfigurationManager(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionDeleting, ResNameConfigurationManager, state.ManagerARN.String(), err), + err.Error(), + ) + return + } + + deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) + _, err = waitConfigurationManagerDeleted(ctx, conn, state.ManagerARN.ValueString(), deleteTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMQuickSetup, create.ErrActionWaitingForDeletion, ResNameConfigurationManager, state.ManagerARN.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceConfigurationManager) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("manager_arn"), req, resp) +} + +func (r *resourceConfigurationManager) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +func waitConfigurationManagerCreated(ctx context.Context, conn *ssmquicksetup.Client, id string, timeout time.Duration) (*ssmquicksetup.GetConfigurationManagerOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.StatusInitializing, awstypes.StatusDeploying), + Target: enum.Slice(awstypes.StatusSucceeded), + Refresh: statusConfigurationManager(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*ssmquicksetup.GetConfigurationManagerOutput); ok { + return out, err + } + + return nil, err +} + +func waitConfigurationManagerUpdated(ctx context.Context, conn *ssmquicksetup.Client, id string, timeout time.Duration) (*ssmquicksetup.GetConfigurationManagerOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.StatusInitializing, awstypes.StatusDeploying), + Target: enum.Slice(awstypes.StatusSucceeded), + Refresh: statusConfigurationManager(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*ssmquicksetup.GetConfigurationManagerOutput); ok { + return out, err + } + + return nil, err +} + +func waitConfigurationManagerDeleted(ctx context.Context, conn *ssmquicksetup.Client, id string, timeout time.Duration) (*ssmquicksetup.GetConfigurationManagerOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.StatusDeploying, awstypes.StatusStopping, awstypes.StatusDeleting), + Target: []string{}, + Refresh: statusConfigurationManager(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*ssmquicksetup.GetConfigurationManagerOutput); ok { + return out, err + } + + return nil, err +} + +func statusConfigurationManager(ctx context.Context, conn *ssmquicksetup.Client, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findConfigurationManagerByID(ctx, conn, id) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + // GetConfigurationManager returns an array of status summaries. The item + // with a "Deployment" type will contain the status of the configuration + // manager during create, update, and delete. + for _, st := range out.StatusSummaries { + if st.StatusType == awstypes.StatusTypeDeployment { + return out, string(st.Status), nil + } + } + + return out, "", nil + } +} + +func findConfigurationManagerByID(ctx context.Context, conn *ssmquicksetup.Client, id string) (*ssmquicksetup.GetConfigurationManagerOutput, error) { + in := &ssmquicksetup.GetConfigurationManagerInput{ + ManagerArn: aws.String(id), + } + + out, err := conn.GetConfigurationManager(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +type resourceConfigurationManagerModel struct { + ConfigurationDefinition fwtypes.ListNestedObjectValueOf[configurationDefinitionModel] `tfsdk:"configuration_definition"` + Description types.String `tfsdk:"description"` + ManagerARN types.String `tfsdk:"manager_arn"` + Name types.String `tfsdk:"name"` + StatusSummaries fwtypes.ListNestedObjectValueOf[statusSummaryModel] `tfsdk:"status_summaries"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type configurationDefinitionModel struct { + ID types.String `tfsdk:"id"` + LocalDeploymentAdministrationRoleARN types.String `tfsdk:"local_deployment_administration_role_arn"` + LocalDeploymentExecutionRoleName types.String `tfsdk:"local_deployment_execution_role_name"` + Parameters fwtypes.MapValueOf[types.String] `tfsdk:"parameters"` + Type types.String `tfsdk:"type"` + TypeVersion types.String `tfsdk:"type_version"` +} + +type statusSummaryModel struct { + Status types.String `tfsdk:"status"` + StatusMessage types.String `tfsdk:"status_message"` + StatusType types.String `tfsdk:"status_type"` +} diff --git a/internal/service/ssmquicksetup/configuration_manager_test.go b/internal/service/ssmquicksetup/configuration_manager_test.go new file mode 100644 index 00000000000..a9fd7151362 --- /dev/null +++ b/internal/service/ssmquicksetup/configuration_manager_test.go @@ -0,0 +1,545 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssmquicksetup_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup" + "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tfssmquicksetup "github.com/hashicorp/terraform-provider-aws/internal/service/ssmquicksetup" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSSMQuickSetupConfigurationManager_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var cm ssmquicksetup.GetConfigurationManagerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmquicksetup_configuration_manager.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSMQuickSetupEndpointID) + testAccConfigurationManagerPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMQuickSetupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationManagerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationManagerConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_definition.*", map[string]string{ + names.AttrType: "AWSQuickSetupType-PatchPolicy", + }), + acctest.MatchResourceAttrRegionalARN(resourceName, "manager_arn", "ssm-quicksetup", regexache.MustCompile(`configuration-manager/+.`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "manager_arn"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "manager_arn", + ImportStateVerifyIgnore: []string{"status_summaries"}, + }, + }, + }) +} + +func TestAccSSMQuickSetupConfigurationManager_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var cm ssmquicksetup.GetConfigurationManagerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmquicksetup_configuration_manager.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSMQuickSetupEndpointID) + testAccConfigurationManagerPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMQuickSetupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationManagerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationManagerConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfssmquicksetup.ResourceConfigurationManager, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccSSMQuickSetupConfigurationManager_description(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var cm ssmquicksetup.GetConfigurationManagerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmquicksetup_configuration_manager.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSMQuickSetupEndpointID) + testAccConfigurationManagerPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMQuickSetupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationManagerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationManagerConfig_description(rName, "foo"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "foo"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "manager_arn"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "manager_arn", + ImportStateVerifyIgnore: []string{"status_summaries"}, + }, + { + Config: testAccConfigurationManagerConfig_description(rName, "bar"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "bar"), + ), + }, + }, + }) +} + +func TestAccSSMQuickSetupConfigurationManager_parameters(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var cm ssmquicksetup.GetConfigurationManagerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmquicksetup_configuration_manager.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSMQuickSetupEndpointID) + testAccConfigurationManagerPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMQuickSetupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationManagerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationManagerConfig_parameters(rName, "10%"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_definition.*", map[string]string{ + names.AttrType: "AWSQuickSetupType-PatchPolicy", + }), + resource.TestCheckResourceAttr(resourceName, "configuration_definition.0.parameters.PatchPolicyName", rName), + resource.TestCheckResourceAttr(resourceName, "configuration_definition.0.parameters.RateControlConcurrency", "10%"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "manager_arn"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "manager_arn", + ImportStateVerifyIgnore: []string{"status_summaries"}, + }, + { + Config: testAccConfigurationManagerConfig_parameters(rName, "15%"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_definition.*", map[string]string{ + names.AttrType: "AWSQuickSetupType-PatchPolicy", + }), + resource.TestCheckResourceAttr(resourceName, "configuration_definition.0.parameters.PatchPolicyName", rName), + resource.TestCheckResourceAttr(resourceName, "configuration_definition.0.parameters.RateControlConcurrency", "15%"), + ), + }, + }, + }) +} + +func TestAccSSMQuickSetupConfigurationManager_tags(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var cm ssmquicksetup.GetConfigurationManagerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmquicksetup_configuration_manager.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSMQuickSetupEndpointID) + testAccConfigurationManagerPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMQuickSetupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationManagerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationManagerConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "manager_arn"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "manager_arn", + ImportStateVerifyIgnore: []string{"status_summaries"}, + }, + { + Config: testAccConfigurationManagerConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "2"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccConfigurationManagerConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationManagerExists(ctx, resourceName, &cm), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + +// Confirms the IAM roles required to execute configuration manager acceptance tests +// are present +// +// The following IAM roles must exist in the account to facilitate execution of +// the CloudFormation templates used to provision patch policies. The test +// configuration __could__ create customer managed roles with these same permissions, +// but due to the complexity of the permissions involved and potential for drift +// it was deemed preferable to rely on the AWS generated roles instead. +// +// To trigger creation of these roles, navigate to the SSM service, select the +// QuickSetup item in the left navbar, and choose the "Patch Manager" type. Leave the +// default configuration in place. Under the "Local deployment roles" section, be +// sure that "User new IAM local deployment roles" is selected. After clicking +// "Create", the new roles will be created prior to the PatchPolicy CloudFormation +// template being executed. The roles will now be available in the account for use with +// acceptance testing. +func testAccConfigurationManagerPreCheck(ctx context.Context, t *testing.T) { + acctest.PreCheckHasIAMRole(ctx, t, "AWS-QuickSetup-PatchPolicy-LocalAdministrationRole") + acctest.PreCheckHasIAMRole(ctx, t, "AWS-QuickSetup-PatchPolicy-LocalExecutionRole") +} + +func testAccCheckConfigurationManagerDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMQuickSetupClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmquicksetup_configuration_manager" { + continue + } + managerARN := rs.Primary.Attributes["manager_arn"] + + _, err := tfssmquicksetup.FindConfigurationManagerByID(ctx, conn, managerARN) + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return create.Error(names.SSMQuickSetup, create.ErrActionCheckingDestroyed, tfssmquicksetup.ResNameConfigurationManager, managerARN, err) + } + + return create.Error(names.SSMQuickSetup, create.ErrActionCheckingDestroyed, tfssmquicksetup.ResNameConfigurationManager, managerARN, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckConfigurationManagerExists(ctx context.Context, name string, configurationmanager *ssmquicksetup.GetConfigurationManagerOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SSMQuickSetup, create.ErrActionCheckingExistence, tfssmquicksetup.ResNameConfigurationManager, name, errors.New("not found")) + } + + managerARN := rs.Primary.Attributes["manager_arn"] + if managerARN == "" { + return create.Error(names.SSMQuickSetup, create.ErrActionCheckingExistence, tfssmquicksetup.ResNameConfigurationManager, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMQuickSetupClient(ctx) + + out, err := tfssmquicksetup.FindConfigurationManagerByID(ctx, conn, managerARN) + if err != nil { + return create.Error(names.SSMQuickSetup, create.ErrActionCheckingExistence, tfssmquicksetup.ResNameConfigurationManager, managerARN, err) + } + + *configurationmanager = *out + + return nil + } +} + +func testAccConfigurationManagerConfigBase_patchPolicy(rName string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} +data "aws_region" "current" {} + +data "aws_ssm_patch_baselines" "test" { + default_baselines = true +} + +locals { + # transform the output of the aws_ssm_patch_baselines data source + # into the format expected by the SelectedPatchBaselines parameter + selected_patch_baselines = jsonencode({ + for baseline in data.aws_ssm_patch_baselines.test.baseline_identities : baseline.operating_system => { + "value" : baseline.baseline_id + "label" : baseline.baseline_name + "description" : baseline.baseline_description + "disabled" : !baseline.default_baseline + } + }) + + local_deployment_administration_role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/AWS-QuickSetup-PatchPolicy-LocalAdministrationRole" + local_deployment_execution_role_name = "AWS-QuickSetup-PatchPolicy-LocalExecutionRole" + + # workaround - terrafmt cannot parse verbs inside nested maps + patch_policy_name = %[1]q +} +`, rName) +} + +func testAccConfigurationManagerConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccConfigurationManagerConfigBase_patchPolicy(rName), + fmt.Sprintf(` +resource "aws_ssmquicksetup_configuration_manager" "test" { + name = %[1]q + + configuration_definition { + local_deployment_administration_role_arn = local.local_deployment_administration_role_arn + local_deployment_execution_role_name = local.local_deployment_execution_role_name + type = "AWSQuickSetupType-PatchPolicy" + + parameters = { + "ConfigurationOptionsPatchOperation" : "Scan", + "ConfigurationOptionsScanValue" : "cron(0 1 * * ? *)", + "ConfigurationOptionsScanNextInterval" : "false", + "PatchBaselineRegion" : data.aws_region.current.name, + "PatchBaselineUseDefault" : "default", + "PatchPolicyName" : local.patch_policy_name, + "SelectedPatchBaselines" : local.selected_patch_baselines, + "OutputLogEnableS3" : "false", + "RateControlConcurrency" : "10%%", + "RateControlErrorThreshold" : "2%%", + "IsPolicyAttachAllowed" : "false", + "TargetAccounts" : data.aws_caller_identity.current.account_id, + "TargetRegions" : data.aws_region.current.name, + "TargetType" : "*" + } + } +} +`, rName)) +} + +func testAccConfigurationManagerConfig_description(rName, description string) string { + return acctest.ConfigCompose( + testAccConfigurationManagerConfigBase_patchPolicy(rName), + fmt.Sprintf(` +resource "aws_ssmquicksetup_configuration_manager" "test" { + name = %[1]q + description = %[2]q + + configuration_definition { + local_deployment_administration_role_arn = local.local_deployment_administration_role_arn + local_deployment_execution_role_name = local.local_deployment_execution_role_name + type = "AWSQuickSetupType-PatchPolicy" + + parameters = { + "ConfigurationOptionsPatchOperation" : "Scan", + "ConfigurationOptionsScanValue" : "cron(0 1 * * ? *)", + "ConfigurationOptionsScanNextInterval" : "false", + "PatchBaselineRegion" : data.aws_region.current.name, + "PatchBaselineUseDefault" : "default", + "PatchPolicyName" : local.patch_policy_name, + "SelectedPatchBaselines" : local.selected_patch_baselines, + "OutputLogEnableS3" : "false", + "RateControlConcurrency" : "10%%", + "RateControlErrorThreshold" : "2%%", + "IsPolicyAttachAllowed" : "false", + "TargetAccounts" : data.aws_caller_identity.current.account_id, + "TargetRegions" : data.aws_region.current.name, + "TargetType" : "*" + } + } +} +`, rName, description)) +} + +func testAccConfigurationManagerConfig_parameters(rName, rateControlConcurrency string) string { + return acctest.ConfigCompose( + testAccConfigurationManagerConfigBase_patchPolicy(rName), + fmt.Sprintf(` +locals { + # workaround - terrafmt cannot parse verbs inside nested maps + rate_control_concurrency = %[2]q +} + +resource "aws_ssmquicksetup_configuration_manager" "test" { + name = %[1]q + + configuration_definition { + local_deployment_administration_role_arn = local.local_deployment_administration_role_arn + local_deployment_execution_role_name = local.local_deployment_execution_role_name + type = "AWSQuickSetupType-PatchPolicy" + + parameters = { + "ConfigurationOptionsPatchOperation" : "Scan", + "ConfigurationOptionsScanValue" : "cron(0 1 * * ? *)", + "ConfigurationOptionsScanNextInterval" : "false", + "PatchBaselineRegion" : data.aws_region.current.name, + "PatchBaselineUseDefault" : "default", + "PatchPolicyName" : local.patch_policy_name, + "SelectedPatchBaselines" : local.selected_patch_baselines, + "OutputLogEnableS3" : "false", + "RateControlConcurrency" : local.rate_control_concurrency, + "RateControlErrorThreshold" : "2%%", + "IsPolicyAttachAllowed" : "false", + "TargetAccounts" : data.aws_caller_identity.current.account_id, + "TargetRegions" : data.aws_region.current.name, + "TargetType" : "*" + } + } +} +`, rName, rateControlConcurrency)) +} + +func testAccConfigurationManagerConfig_tags1(rName, key1, value1 string) string { + return acctest.ConfigCompose( + testAccConfigurationManagerConfigBase_patchPolicy(rName), + fmt.Sprintf(` +resource "aws_ssmquicksetup_configuration_manager" "test" { + name = %[1]q + + configuration_definition { + local_deployment_administration_role_arn = local.local_deployment_administration_role_arn + local_deployment_execution_role_name = local.local_deployment_execution_role_name + type = "AWSQuickSetupType-PatchPolicy" + + parameters = { + "ConfigurationOptionsPatchOperation" : "Scan", + "ConfigurationOptionsScanValue" : "cron(0 1 * * ? *)", + "ConfigurationOptionsScanNextInterval" : "false", + "PatchBaselineRegion" : data.aws_region.current.name, + "PatchBaselineUseDefault" : "default", + "PatchPolicyName" : local.patch_policy_name, + "SelectedPatchBaselines" : local.selected_patch_baselines, + "OutputLogEnableS3" : "false", + "RateControlConcurrency" : "10%%", + "RateControlErrorThreshold" : "2%%", + "IsPolicyAttachAllowed" : "false", + "TargetAccounts" : data.aws_caller_identity.current.account_id, + "TargetRegions" : data.aws_region.current.name, + "TargetType" : "*" + } + } + + tags = { + %[2]q = %[3]q + } +} +`, rName, key1, value1)) +} + +func testAccConfigurationManagerConfig_tags2(rName, key1, value1, key2, value2 string) string { + return acctest.ConfigCompose( + testAccConfigurationManagerConfigBase_patchPolicy(rName), + fmt.Sprintf(` +resource "aws_ssmquicksetup_configuration_manager" "test" { + name = %[1]q + + configuration_definition { + local_deployment_administration_role_arn = local.local_deployment_administration_role_arn + local_deployment_execution_role_name = local.local_deployment_execution_role_name + type = "AWSQuickSetupType-PatchPolicy" + + parameters = { + "ConfigurationOptionsPatchOperation" : "Scan", + "ConfigurationOptionsScanValue" : "cron(0 1 * * ? *)", + "ConfigurationOptionsScanNextInterval" : "false", + "PatchBaselineRegion" : data.aws_region.current.name, + "PatchBaselineUseDefault" : "default", + "PatchPolicyName" : local.patch_policy_name, + "SelectedPatchBaselines" : local.selected_patch_baselines, + "OutputLogEnableS3" : "false", + "RateControlConcurrency" : "10%%", + "RateControlErrorThreshold" : "2%%", + "IsPolicyAttachAllowed" : "false", + "TargetAccounts" : data.aws_caller_identity.current.account_id, + "TargetRegions" : data.aws_region.current.name, + "TargetType" : "*" + } + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, key1, value1, key2, value2)) +} diff --git a/internal/service/ssmquicksetup/exports_test.go b/internal/service/ssmquicksetup/exports_test.go new file mode 100644 index 00000000000..d14cb81917d --- /dev/null +++ b/internal/service/ssmquicksetup/exports_test.go @@ -0,0 +1,11 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssmquicksetup + +// Exports for use in tests only. +var ( + ResourceConfigurationManager = newResourceConfigurationManager + + FindConfigurationManagerByID = findConfigurationManagerByID +) diff --git a/internal/service/ssmquicksetup/generate.go b/internal/service/ssmquicksetup/generate.go index 2c9bdbda9bc..3991d1aebeb 100644 --- a/internal/service/ssmquicksetup/generate.go +++ b/internal/service/ssmquicksetup/generate.go @@ -2,6 +2,8 @@ // SPDX-License-Identifier: MPL-2.0 //go:generate go run ../../generate/servicepackage/main.go +//go:generate go run ../../generate/tags/main.go -ServiceTagsMap -UpdateTags -SkipTypesImp -KVTValues +//go:generate go run ../../generate/tags/main.go -ServiceTagsSlice -ListTags -TagType TagEntry -KeyValueTagsFunc KeyValueTagsSlice -TagsFunc TagsSlice -GetTagsInFunc getTagsInSlice -SetTagsOutFunc setTagsOutSlice tags_slice_gen.go // ONLY generate directives and package declaration! Do not add anything else to this file. package ssmquicksetup diff --git a/internal/service/ssmquicksetup/service_package_gen.go b/internal/service/ssmquicksetup/service_package_gen.go index 9180511d137..a45637a6984 100644 --- a/internal/service/ssmquicksetup/service_package_gen.go +++ b/internal/service/ssmquicksetup/service_package_gen.go @@ -19,7 +19,15 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceConfigurationManager, + Name: "Configuration Manager", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "manager_arn", + }, + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { diff --git a/internal/service/ssmquicksetup/tags_gen.go b/internal/service/ssmquicksetup/tags_gen.go new file mode 100644 index 00000000000..15650436fa4 --- /dev/null +++ b/internal/service/ssmquicksetup/tags_gen.go @@ -0,0 +1,95 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package ssmquicksetup + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/logging" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/types/option" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// map[string]string handling + +// Tags returns ssmquicksetup service tags. +func Tags(tags tftags.KeyValueTags) map[string]string { + return tags.Map() +} + +// KeyValueTags creates tftags.KeyValueTags from ssmquicksetup service tags. +func KeyValueTags(ctx context.Context, tags map[string]string) tftags.KeyValueTags { + return tftags.New(ctx, tags) +} + +// getTagsIn returns ssmquicksetup service tags from Context. +// nil is returned if there are no input tags. +func getTagsIn(ctx context.Context) map[string]string { + if inContext, ok := tftags.FromContext(ctx); ok { + if tags := Tags(inContext.TagsIn.UnwrapOrDefault()); len(tags) > 0 { + return tags + } + } + + return nil +} + +// setTagsOut sets ssmquicksetup service tags in Context. +func setTagsOut(ctx context.Context, tags map[string]string) { + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = option.Some(KeyValueTags(ctx, tags)) + } +} + +// updateTags updates ssmquicksetup service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func updateTags(ctx context.Context, conn *ssmquicksetup.Client, identifier string, oldTagsMap, newTagsMap any, optFns ...func(*ssmquicksetup.Options)) error { + oldTags := tftags.New(ctx, oldTagsMap) + newTags := tftags.New(ctx, newTagsMap) + + ctx = tflog.SetField(ctx, logging.KeyResourceId, identifier) + + removedTags := oldTags.Removed(newTags) + removedTags = removedTags.IgnoreSystem(names.SSMQuickSetup) + if len(removedTags) > 0 { + input := &ssmquicksetup.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: removedTags.Keys(), + } + + _, err := conn.UntagResource(ctx, input, optFns...) + + if err != nil { + return fmt.Errorf("untagging resource (%s): %w", identifier, err) + } + } + + updatedTags := oldTags.Updated(newTags) + updatedTags = updatedTags.IgnoreSystem(names.SSMQuickSetup) + if len(updatedTags) > 0 { + input := &ssmquicksetup.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: Tags(updatedTags), + } + + _, err := conn.TagResource(ctx, input, optFns...) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} + +// UpdateTags updates ssmquicksetup service tags. +// It is called from outside this package. +func (p *servicePackage) UpdateTags(ctx context.Context, meta any, identifier string, oldTags, newTags any) error { + return updateTags(ctx, meta.(*conns.AWSClient).SSMQuickSetupClient(ctx), identifier, oldTags, newTags) +} diff --git a/internal/service/ssmquicksetup/tags_slice_gen.go b/internal/service/ssmquicksetup/tags_slice_gen.go new file mode 100644 index 00000000000..a4c8e4876de --- /dev/null +++ b/internal/service/ssmquicksetup/tags_slice_gen.go @@ -0,0 +1,94 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package ssmquicksetup + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup/types" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/types/option" +) + +// listTags lists ssmquicksetup service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func listTags(ctx context.Context, conn *ssmquicksetup.Client, identifier string, optFns ...func(*ssmquicksetup.Options)) (tftags.KeyValueTags, error) { + input := &ssmquicksetup.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(ctx, input, optFns...) + + if err != nil { + return tftags.New(ctx, nil), err + } + + return KeyValueTagsSlice(ctx, output.Tags), nil +} + +// ListTags lists ssmquicksetup service tags and set them in Context. +// It is called from outside this package. +func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier string) error { + tags, err := listTags(ctx, meta.(*conns.AWSClient).SSMQuickSetupClient(ctx), identifier) + + if err != nil { + return err + } + + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = option.Some(tags) + } + + return nil +} + +// []*SERVICE.Tag handling + +// TagsSlice returns ssmquicksetup service tags. +func TagsSlice(tags tftags.KeyValueTags) []awstypes.TagEntry { + result := make([]awstypes.TagEntry, 0, len(tags)) + + for k, v := range tags.Map() { + tag := awstypes.TagEntry{ + Key: aws.String(k), + Value: aws.String(v), + } + + result = append(result, tag) + } + + return result +} + +// KeyValueTagsSlice creates tftags.KeyValueTags from ssmquicksetup service tags. +func KeyValueTagsSlice(ctx context.Context, tags []awstypes.TagEntry) tftags.KeyValueTags { + m := make(map[string]*string, len(tags)) + + for _, tag := range tags { + m[aws.ToString(tag.Key)] = tag.Value + } + + return tftags.New(ctx, m) +} + +// getTagsInSlice returns ssmquicksetup service tags from Context. +// nil is returned if there are no input tags. +func getTagsInSlice(ctx context.Context) []awstypes.TagEntry { + if inContext, ok := tftags.FromContext(ctx); ok { + if tags := TagsSlice(inContext.TagsIn.UnwrapOrDefault()); len(tags) > 0 { + return tags + } + } + + return nil +} + +// setTagsOutSlice sets ssmquicksetup service tags in Context. +func setTagsOutSlice(ctx context.Context, tags []awstypes.TagEntry) { + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = option.Some(KeyValueTagsSlice(ctx, tags)) + } +} diff --git a/names/names.go b/names/names.go index 1ef40ab6dee..8819bf43d20 100644 --- a/names/names.go +++ b/names/names.go @@ -127,6 +127,7 @@ const ( ServiceCatalogEndpointID = "servicecatalog" SSMEndpointID = "ssm" SSMIncidentsEndpointID = "ssm-incidents" + SSMQuickSetupEndpointID = "ssm-quicksetup" SSOAdminEndpointID = "sso" STSEndpointID = "sts" SchedulerEndpointID = "scheduler" diff --git a/website/docs/r/ssmquicksetup_configuration_manager.html.markdown b/website/docs/r/ssmquicksetup_configuration_manager.html.markdown new file mode 100644 index 00000000000..f4ff9e05526 --- /dev/null +++ b/website/docs/r/ssmquicksetup_configuration_manager.html.markdown @@ -0,0 +1,123 @@ +--- +subcategory: "SSM Quick Setup" +layout: "aws" +page_title: "AWS: aws_ssmquicksetup_configuration_manager" +description: |- + Terraform resource for managing an AWS SSM Quick Setup Configuration Manager. +--- +# Resource: aws_ssmquicksetup_configuration_manager + +Terraform resource for managing an AWS SSM Quick Setup Configuration Manager. + +## Example Usage + +### Patch Policy Configuration Type + +```terraform +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} +data "aws_region" "current" {} + +data "aws_ssm_patch_baselines" "example" { + default_baselines = true +} + +locals { + # transform the output of the aws_ssm_patch_baselines data source + # into the format expected by the SelectedPatchBaselines parameter + selected_patch_baselines = jsonencode({ + for baseline in data.aws_ssm_patch_baselines.example.baseline_identities : baseline.operating_system => { + "value" : baseline.baseline_id + "label" : baseline.baseline_name + "description" : baseline.baseline_description + "disabled" : !baseline.default_baseline + } + }) +} + +resource "aws_ssmquicksetup_configuration_manager" "example" { + name = "example" + + configuration_definition { + local_deployment_administration_role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/AWS-QuickSetup-PatchPolicy-LocalAdministrationRole" + local_deployment_execution_role_name = "AWS-QuickSetup-PatchPolicy-LocalExecutionRole" + type = "AWSQuickSetupType-PatchPolicy" + + parameters = { + "ConfigurationOptionsPatchOperation" : "Scan", + "ConfigurationOptionsScanValue" : "cron(0 1 * * ? *)", + "ConfigurationOptionsScanNextInterval" : "false", + "PatchBaselineRegion" : data.aws_region.current.name, + "PatchBaselineUseDefault" : "default", + "PatchPolicyName" : "example", + "SelectedPatchBaselines" : local.selected_patch_baselines, + "OutputLogEnableS3" : "false", + "RateControlConcurrency" : "10%", + "RateControlErrorThreshold" : "2%", + "IsPolicyAttachAllowed" : "false", + "TargetAccounts" : data.aws_caller_identity.current.account_id, + "TargetRegions" : data.aws_region.current.name, + "TargetType" : "*" + } + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `configuration_definition` - (Required) Definition of the Quick Setup configuration that the configuration manager deploys. See [`configuration_definition`](#configuration_definition-argument-reference) below. +* `name` - (Required) Configuration manager name. + +The following arguments are optional: + +* `description` - (Optional) Description of the configuration manager. +* `tags` - (Optional) Map of tags assigned to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +### `configuration_definition` Argument Reference + +* `local_deployment_administrator_role_arn` - (Optional) ARN of the IAM role used to administrate local configuration deployments. +* `local_deployment_execution_role_name` - (Optional) Name of the IAM role used to deploy local configurations. +* `parameters` - (Required) Parameters for the configuration definition type. Parameters for configuration definitions vary based the configuration type. See the [AWS API documentation](https://docs.aws.amazon.com/quick-setup/latest/APIReference/API_ConfigurationDefinitionInput.html) for a complete list of parameters for each configuration type. +* `type` - (Required) Type of the Quick Setup configuration. +* `type_version` - (Optional) Version of the Quick Setup type to use. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `manager_arn` - ARN of the Configuration Manager. +* `status_summaries` - A summary of the state of the configuration manager. This includes deployment statuses, association statuses, drift statuses, health checks, and more. See [`status_summaries`](#status_summaries-attribute-reference) below. +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +### `status_summaries` Attribute Reference + +* `status` - Current status. +* `status_message` - When applicable, returns an informational message relevant to the current status and status type of the status summary object. +* `status_type` - Type of a status summary. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `20m`) +* `update` - (Default `20m`) +* `delete` - (Default `20m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import SSM Quick Setup Configuration Manager using the `manager_arn`. For example: + +```terraform +import { + to = aws_ssmquicksetup_configuration_manager.example + id = "arn:aws:ssm-quicksetup:us-east-1:012345678901:configuration-manager/abcd-1234" +} +``` + +Using `terraform import`, import SSM Quick Setup Configuration Manager using the `manager_arn`. For example: + +```console +% terraform import aws_ssmquicksetup_configuration_manager.example arn:aws:ssm-quicksetup:us-east-1:012345678901:configuration-manager/abcd-1234 +```