From b3ece2725bb2aafff1cde736f72fd493a18b10d9 Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Fri, 14 Apr 2023 09:06:46 +0100 Subject: [PATCH 01/15] Started wirting the vpc lattice listener rule --- internal/service/vpclattice/listener_rule.go | 756 ++++++++++++++++++ .../service/vpclattice/listener_rule_test.go | 310 +++++++ .../service/vpclattice/service_package_gen.go | 8 + .../r/vpclattice_listener_rule.html.markdown | 53 ++ 4 files changed, 1127 insertions(+) create mode 100644 internal/service/vpclattice/listener_rule.go create mode 100644 internal/service/vpclattice/listener_rule_test.go create mode 100644 website/docs/r/vpclattice_listener_rule.html.markdown diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go new file mode 100644 index 00000000000..5962226261e --- /dev/null +++ b/internal/service/vpclattice/listener_rule.go @@ -0,0 +1,756 @@ +package vpclattice + +import ( + "context" + "errors" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/vpclattice" + "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "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/structure" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_vpclattice_listener_rule", name="Listener Rule") +// @Tags(identifierAttribute="arn") +func ResourceListenerRule() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceListenerRuleCreate, + ReadWithoutTimeout: resourceListenerRuleRead, + UpdateWithoutTimeout: resourceListenerRuleUpdate, + DeleteWithoutTimeout: resourceListenerRuleDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "action": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fixed_response": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status_code": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(100, 599), + }, + }, + }, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, + }, + "forward": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target_group": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target_group_identifier": { + Type: schema.TypeString, + Required: true, + }, + "weight": { + Type: schema.TypeInt, + ValidateFunc: validation.IntBetween(0, 999), + Default: 1, + Optional: true, + }, + }, + }, + }, + }, + }, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, + }, + }, + }, + }, + "match": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "http_match": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "headers_match": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "case_sensitive": { + Type: schema.TypeBool, + Optional: true, + }, + "match": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "contains": { + Type: schema.TypeString, + Required: true, + }, + "exact": { + Type: schema.TypeString, + Required: true, + }, + "prefix": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "method": { + Type: schema.TypeString, + Computed: true, + }, + "path_match": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "case_sensitive": { + Type: schema.TypeBool, + Optional: true, + }, + "match": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "exact": { + Type: schema.TypeString, + Required: true, + }, + "prefix": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(3, 128), + }, + "priority": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: false, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: customdiff.All( + verify.SetTagsDiff, + func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + targetGroupType := types.TargetGroupType(d.Get("type").(string)) + + if v, ok := d.GetOk("config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + if targetGroupType == types.TargetGroupTypeLambda { + return fmt.Errorf(`config not supported for type = %q`, targetGroupType) + } + } else { + if targetGroupType != types.TargetGroupTypeLambda { + return fmt.Errorf(`config required for type = %q`, targetGroupType) + } + } + + return nil + }, + ), + } +} + +const ( + ResNameListenerRule = "Listener Rule" +) + +func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + // TIP: -- 2. Populate a create input structure + in := &vpclattice.CreateListenerInput{ + Name: aws.String(d.Get("name").(string)), + // ServiceIdentifier: vpclattice.CreateRuleInput.ServiceIdentifier() + } + + // if v, ok := d.GetOk("max_size"); ok { + // // TIP: Optional fields should be set based on whether or not they are + // // used. + // in.MaxSize = aws.Int64(int64(v.(int))) + // } + + // if v, ok := d.GetOk("complex_argument"); ok && len(v.([]interface{})) > 0 { + // // TIP: Use an expander to assign a complex argument. + // in.ComplexArguments = expandComplexArguments(v.([]interface{})) + // } + + // TIP: Not all resources support tags and tags don't always make sense. If + // your resource doesn't need tags, you can remove the tags lines here and + // below. Many resources do include tags so this a reminder to include them + // where possible. + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } + + // TIP: -- 3. Call the AWS create function + out, err := conn.CreateListenerRule(ctx, in) + if err != nil { + // TIP: Since d.SetId() has not been called yet, you cannot use d.Id() + // in error messages at this point. + return create.DiagError(names.VpcLattice, create.ErrActionCreating, ResNameListenerRule, d.Get("name").(string), err) + } + + if out == nil || out.ListenerRule == nil { + return create.DiagError(names.VpcLattice, create.ErrActionCreating, ResNameListenerRule, d.Get("name").(string), errors.New("empty output")) + } + + // TIP: -- 4. Set the minimum arguments and/or attributes for the Read function to + // work. + d.SetId(aws.ToString(out.ListenerRule.ListenerRuleID)) + + // TIP: -- 5. Use a waiter to wait for create to complete + if _, err := waitListenerRuleCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionWaitingForCreation, ResNameListenerRule, d.Id(), err) + } + + // TIP: -- 6. Call the Read function in the Create return + return resourceListenerRuleRead(ctx, d, meta) +} + +func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // TIP: ==== RESOURCE READ ==== + // Generally, the Read function should do the following things. Make + // sure there is a good reason if you don't do one of these. + // + // 1. Get a client connection to the relevant service + // 2. Get the resource from AWS + // 3. Set ID to empty where resource is not new and not found + // 4. Set the arguments and attributes + // 5. Set the tags + // 6. Return nil + + // TIP: -- 1. Get a client connection to the relevant service + conn := meta.(*conns.AWSClient).VpcLatticeClient() + + // TIP: -- 2. Get the resource from AWS using an API Get, List, or Describe- + // type function, or, better yet, using a finder. + out, err := findListenerRuleByID(ctx, conn, d.Id()) + + // TIP: -- 3. Set ID to empty where resource is not new and not found + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] VpcLattice ListenerRule (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionReading, ResNameListenerRule, d.Id(), err) + } + + // TIP: -- 4. Set the arguments and attributes + // + // For simple data types (i.e., schema.TypeString, schema.TypeBool, + // schema.TypeInt, and schema.TypeFloat), a simple Set call (e.g., + // d.Set("arn", out.Arn) is sufficient. No error or nil checking is + // necessary. + // + // However, there are some situations where more handling is needed. + // a. Complex data types (e.g., schema.TypeList, schema.TypeSet) + // b. Where errorneous diffs occur. For example, a schema.TypeString may be + // a JSON. AWS may return the JSON in a slightly different order but it + // is equivalent to what is already set. In that case, you may check if + // it is equivalent before setting the different JSON. + d.Set("arn", out.Arn) + d.Set("name", out.Name) + + // TIP: Setting a complex type. + // For more information, see: + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#data-handling-and-conversion + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#flatten-functions-for-blocks + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#root-typeset-of-resource-and-aws-list-of-structure + if err := d.Set("complex_argument", flattenComplexArguments(out.ComplexArguments)); err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) + } + + // TIP: Setting a JSON string to avoid errorneous diffs. + p, err := verify.SecondJSONUnlessEquivalent(d.Get("policy").(string), aws.ToString(out.Policy)) + if err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) + } + + p, err = structure.NormalizeJsonString(p) + if err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) + } + + d.Set("policy", p) + + // TIP: -- 5. Set the tags + // + // TIP: Not all resources support tags and tags don't always make sense. If + // your resource doesn't need tags, you can remove the tags lines here and + // below. Many resources do include tags so this a reminder to include them + // where possible. + tags, err := ListTags(ctx, conn, d.Id()) + if err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionReading, ResNameListenerRule, d.Id(), err) + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) + } + + // TIP: -- 6. Return nil + return nil +} + +func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // TIP: ==== RESOURCE UPDATE ==== + // Not all resources have Update functions. There are a few reasons: + // a. The AWS API does not support changing a resource + // b. All arguments have ForceNew: true, set + // c. The AWS API uses a create call to modify an existing resource + // + // In the cases of a. and b., the main resource function will not have a + // UpdateWithoutTimeout defined. In the case of c., Update and Create are + // the same. + // + // The rest of the time, there should be an Update function and it should + // do the following things. Make sure there is a good reason if you don't + // do one of these. + // + // 1. Get a client connection to the relevant service + // 2. Populate a modify input structure and check for changes + // 3. Call the AWS modify/update function + // 4. Use a waiter to wait for update to complete + // 5. Call the Read function in the Update return + + // TIP: -- 1. Get a client connection to the relevant service + conn := meta.(*conns.AWSClient).VpcLatticeClient() + + // TIP: -- 2. Populate a modify input structure and check for changes + // + // When creating the input structure, only include mandatory fields. Other + // fields are set as needed. You can use a flag, such as update below, to + // determine if a certain portion of arguments have been changed and + // whether to call the AWS update function. + update := false + + in := &vpclattice.UpdateListenerRuleInput{ + Id: aws.String(d.Id()), + } + + if d.HasChanges("an_argument") { + in.AnArgument = aws.String(d.Get("an_argument").(string)) + update = true + } + + if !update { + // TIP: If update doesn't do anything at all, which is rare, you can + // return nil. Otherwise, return a read call, as below. + return nil + } + + // TIP: -- 3. Call the AWS modify/update function + log.Printf("[DEBUG] Updating VpcLattice ListenerRule (%s): %#v", d.Id(), in) + out, err := conn.UpdateListenerRule(ctx, in) + if err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionUpdating, ResNameListenerRule, d.Id(), err) + } + + // TIP: -- 4. Use a waiter to wait for update to complete + if _, err := waitListenerRuleUpdated(ctx, conn, aws.ToString(out.OperationId), d.Timeout(schema.TimeoutUpdate)); err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionWaitingForUpdate, ResNameListenerRule, d.Id(), err) + } + + // TIP: -- 5. Call the Read function in the Update return + return resourceListenerRuleRead(ctx, d, meta) +} + +func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // TIP: ==== RESOURCE DELETE ==== + // Most resources have Delete functions. There are rare situations + // where you might not need a delete: + // a. The AWS API does not provide a way to delete the resource + // b. The point of your resource is to perform an action (e.g., reboot a + // server) and deleting serves no purpose. + // + // The Delete function should do the following things. Make sure there + // is a good reason if you don't do one of these. + // + // 1. Get a client connection to the relevant service + // 2. Populate a delete input structure + // 3. Call the AWS delete function + // 4. Use a waiter to wait for delete to complete + // 5. Return nil + + // TIP: -- 1. Get a client connection to the relevant service + conn := meta.(*conns.AWSClient).VpcLatticeClient() + + // TIP: -- 2. Populate a delete input structure + log.Printf("[INFO] Deleting VpcLattice ListenerRule %s", d.Id()) + + // TIP: -- 3. Call the AWS delete function + _, err := conn.DeleteListenerRule(ctx, &vpclattice.DeleteListenerRuleInput{ + Id: aws.String(d.Id()), + }) + + // TIP: On rare occassions, the API returns a not found error after deleting a + // resource. If that happens, we don't want it to show up as an error. + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.VpcLattice, create.ErrActionDeleting, ResNameListenerRule, d.Id(), err) + } + + // TIP: -- 4. Use a waiter to wait for delete to complete + if _, err := waitListenerRuleDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return create.DiagError(names.VpcLattice, create.ErrActionWaitingForDeletion, ResNameListenerRule, d.Id(), err) + } + + // TIP: -- 5. Return nil + return nil +} + +// TIP: ==== STATUS CONSTANTS ==== +// Create constants for states and statuses if the service does not +// already have suitable constants. We prefer that you use the constants +// provided in the service if available (e.g., amp.WorkspaceStatusCodeActive). +const ( + statusChangePending = "Pending" + statusDeleting = "Deleting" + statusNormal = "Normal" + statusUpdated = "Updated" +) + +// TIP: ==== WAITERS ==== +// Some resources of some services have waiters provided by the AWS API. +// Unless they do not work properly, use them rather than defining new ones +// here. +// +// Sometimes we define the wait, status, and find functions in separate +// files, wait.go, status.go, and find.go. Follow the pattern set out in the +// service and define these where it makes the most sense. +// +// If these functions are used in the _test.go file, they will need to be +// exported (i.e., capitalized). +// +// You will need to adjust the parameters and names to fit the service. + +func waitListenerRuleCreated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{}, + Target: []string{statusNormal}, + Refresh: statusListenerRule(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { + return out, err + } + + return nil, err +} + +// TIP: It is easier to determine whether a resource is updated for some +// resources than others. The best case is a status flag that tells you when +// the update has been fully realized. Other times, you can check to see if a +// key resource argument is updated to a new value or not. + +func waitListenerRuleUpdated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{statusChangePending}, + Target: []string{statusUpdated}, + Refresh: statusListenerRule(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { + return out, err + } + + return nil, err +} + +// TIP: A deleted waiter is almost like a backwards created waiter. There may +// be additional pending states, however. + +func waitListenerRuleDeleted(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{statusDeleting, statusNormal}, + Target: []string{}, + Refresh: statusListenerRule(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { + return out, err + } + + return nil, err +} + +// TIP: ==== STATUS ==== +// The status function can return an actual status when that field is +// available from the API (e.g., out.Status). Otherwise, you can use custom +// statuses to communicate the states of the resource. +// +// Waiters consume the values returned by status functions. Design status so +// that it can be reused by a create, update, and delete waiter, if possible. + +func statusListenerRule(ctx context.Context, conn *vpclattice.Client, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findListenerRuleByID(ctx, conn, id) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, aws.ToString(out.Status), nil + } +} + +// TIP: ==== FINDERS ==== +// The find function is not strictly necessary. You could do the API +// request from the status function. However, we have found that find often +// comes in handy in other places besides the status function. As a result, it +// is good practice to define it separately. + +func findListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.ListenerRule, error) { + in := &vpclattice.GetListenerRuleInput{ + Id: aws.String(id), + } + out, err := conn.GetListenerRule(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil || out.ListenerRule == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out.ListenerRule, nil +} + +// TIP: ==== FLEX ==== +// Flatteners and expanders ("flex" functions) help handle complex data +// types. Flatteners take an API data type and return something you can use in +// a d.Set() call. In other words, flatteners translate from AWS -> Terraform. +// +// On the other hand, expanders take a Terraform data structure and return +// something that you can send to the AWS API. In other words, expanders +// translate from Terraform -> AWS. +// +// See more: +// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ +func flattenComplexArgument(apiObject *vpclattice.ComplexArgument) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SubFieldOne; v != nil { + m["sub_field_one"] = aws.ToString(v) + } + + if v := apiObject.SubFieldTwo; v != nil { + m["sub_field_two"] = aws.ToString(v) + } + + return m +} + +// TIP: Often the AWS API will return a slice of structures in response to a +// request for information. Sometimes you will have set criteria (e.g., the ID) +// that means you'll get back a one-length slice. This plural function works +// brilliantly for that situation too. +func flattenComplexArguments(apiObjects []*vpclattice.ComplexArgument) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + l = append(l, flattenComplexArgument(apiObject)) + } + + return l +} + +// TIP: Remember, as mentioned above, expanders take a Terraform data structure +// and return something that you can send to the AWS API. In other words, +// expanders translate from Terraform -> AWS. +// +// See more: +// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ +func expandComplexArgument(tfMap map[string]interface{}) *vpclattice.ComplexArgument { + if tfMap == nil { + return nil + } + + a := &vpclattice.ComplexArgument{} + + if v, ok := tfMap["sub_field_one"].(string); ok && v != "" { + a.SubFieldOne = aws.String(v) + } + + if v, ok := tfMap["sub_field_two"].(string); ok && v != "" { + a.SubFieldTwo = aws.String(v) + } + + return a +} + +// TIP: Even when you have a list with max length of 1, this plural function +// works brilliantly. However, if the AWS API takes a structure rather than a +// slice of structures, you will not need it. +func expandComplexArguments(tfList []interface{}) []*vpclattice.ComplexArgument { + // TIP: The AWS API can be picky about whether you send a nil or zero- + // length for an argument that should be cleared. For example, in some + // cases, if you send a nil value, the AWS API interprets that as "make no + // changes" when what you want to say is "remove everything." Sometimes + // using a zero-length list will cause an error. + // + // As a result, here are two options. Usually, option 1, nil, will work as + // expected, clearing the field. But, test going from something to nothing + // to make sure it works. If not, try the second option. + + // TIP: Option 1: Returning nil for zero-length list + if len(tfList) == 0 { + return nil + } + + var s []*vpclattice.ComplexArgument + + // TIP: Option 2: Return zero-length list for zero-length list. If option 1 does + // not work, after testing going from something to nothing (if that is + // possible), uncomment out the next line and remove option 1. + // + // s := make([]*vpclattice.ComplexArgument, 0) + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + a := expandComplexArgument(m) + + if a == nil { + continue + } + + s = append(s, a) + } + + return s +} diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go new file mode 100644 index 00000000000..110baa2d52c --- /dev/null +++ b/internal/service/vpclattice/listener_rule_test.go @@ -0,0 +1,310 @@ +package vpclattice_test + +// **PLEASE DELETE THIS AND ALL TIP COMMENTS BEFORE SUBMITTING A PR FOR REVIEW!** +// +// TIP: ==== INTRODUCTION ==== +// Thank you for trying the skaff tool! +// +// You have opted to include these helpful comments. They all include "TIP:" +// to help you find and remove them when you're done with them. +// +// While some aspects of this file are customized to your input, the +// scaffold tool does *not* look at the AWS API and ensure it has correct +// function, structure, and variable names. It makes guesses based on +// commonalities. You will need to make significant adjustments. +// +// In other words, as generated, this is a rough outline of the work you will +// need to do. If something doesn't make sense for your situation, get rid of +// it. +// +// Remember to register this new resource in the provider +// (internal/provider/provider.go) once you finish. Otherwise, Terraform won't +// know about it. + +import ( + // TIP: ==== IMPORTS ==== + // This is a common set of imports but not customized to your code since + // your code hasn't been written yet. Make sure you, your IDE, or + // goimports -w fixes these imports. + // + // The provider linter wants your imports to be in two groups: first, + // standard library (i.e., "fmt" or "strings"), second, everything else. + // + // Also, AWS Go SDK v2 may handle nested structures differently than v1, + // using the services/vpclattice/types package. If so, you'll + // need to import types and reference the nested types, e.g., as + // types.. + "context" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/vpclattice" + "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + + // TIP: You will often need to import the package that this test file lives + // in. Since it is in the "test" context, it must import the package to use + // any normal context constants, variables, or functions. + tfvpclattice "github.com/hashicorp/terraform-provider-aws/internal/service/vpclattice" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// TIP: File Structure. The basic outline for all test files should be as +// follows. Improve this resource's maintainability by following this +// outline. +// +// 1. Package declaration (add "_test" since this is a test file) +// 2. Imports +// 3. Unit tests +// 4. Basic test +// 5. Disappears test +// 6. All the other tests +// 7. Helper functions (exists, destroy, check, etc.) +// 8. Functions that return Terraform configurations + +// TIP: ==== UNIT TESTS ==== +// This is an example of a unit test. Its name is not prefixed with +// "TestAcc" like an acceptance test. +// +// Unlike acceptance tests, unit tests do not access AWS and are focused on a +// function (or method). Because of this, they are quick and cheap to run. +// +// In designing a resource's implementation, isolate complex bits from AWS bits +// so that they can be tested through a unit test. We encourage more unit tests +// in the provider. +// +// Cut and dry functions using well-used patterns, like typical flatteners and +// expanders, don't need unit testing. However, if they are complex or +// intricate, they should be unit tested. +func TestListenerRuleExampleUnitTest(t *testing.T) { + testCases := []struct { + TestName string + Input string + Expected string + Error bool + }{ + { + TestName: "empty", + Input: "", + Expected: "", + Error: true, + }, + { + TestName: "descriptive name", + Input: "some input", + Expected: "some output", + Error: false, + }, + { + TestName: "another descriptive name", + Input: "more input", + Expected: "more output", + Error: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + got, err := tfvpclattice.FunctionFromResource(testCase.Input) + + if err != nil && !testCase.Error { + t.Errorf("got error (%s), expected no error", err) + } + + if err == nil && testCase.Error { + t.Errorf("got (%s) and no error, expected error", got) + } + + if got != testCase.Expected { + t.Errorf("got %s, expected %s", got, testCase.Expected) + } + }) + } +} + +// TIP: ==== ACCEPTANCE TESTS ==== +// This is an example of a basic acceptance test. This should test as much of +// standard functionality of the resource as possible, and test importing, if +// applicable. We prefix its name with "TestAcc", the service, and the +// resource name. +// +// Acceptance test access AWS and cost money to run. +func TestAccVpcLatticeListenerRule_basic(t *testing.T) { + ctx := acctest.Context(t) + // TIP: This is a long-running test guard for tests that run longer than + // 300s (5 min) generally. + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var listenerrule vpclattice.DescribeListenerRuleResponse + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_listener_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(t, names.VpcLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VpcLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckListenerRuleDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccListenerRuleConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckListenerRuleExists(ctx, resourceName, &listenerrule), + resource.TestCheckResourceAttr(resourceName, "auto_minor_version_upgrade", "false"), + resource.TestCheckResourceAttrSet(resourceName, "maintenance_window_start_time.0.day_of_week"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "user.*", map[string]string{ + "console_access": "false", + "groups.#": "0", + "username": "Test", + "password": "TestTest1234", + }), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpclattice", regexp.MustCompile(`listenerrule:+.`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccVpcLatticeListenerRule_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var listenerrule vpclattice.DescribeListenerRuleResponse + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_listener_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(t, names.VpcLatticeEndpointID) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VpcLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckListenerRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccListenerRuleConfig_basic(rName, testAccListenerRuleVersionNewer), + Check: resource.ComposeTestCheckFunc( + testAccCheckListenerRuleExists(resourceName, &listenerrule), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfvpclattice.ResourceListenerRule(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckListenerRuleDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).VpcLatticeClient() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpclattice_listener_rule" { + continue + } + + input := &vpclattice.DescribeListenerRuleInput{ + ListenerRuleId: aws.String(rs.Primary.ID), + } + _, err := conn.DescribeListenerRule(ctx, &vpclattice.DescribeListenerRuleInput{ + ListenerRuleId: aws.String(rs.Primary.ID), + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } + + return create.Error(names.VpcLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameListenerRule, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckListenerRuleExists(ctx context.Context, name string, listenerrule *vpclattice.DescribeListenerRuleResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.VpcLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.VpcLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).VpcLatticeClient() + resp, err := conn.DescribeListenerRule(ctx, &vpclattice.DescribeListenerRuleInput{ + ListenerRuleId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.VpcLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, rs.Primary.ID, err) + } + + *listenerrule = *resp + + return nil + } +} + +func testAccCheckListenerRuleNotRecreated(before, after *vpclattice.DescribeListenerRuleResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.StringValue(before.ListenerRuleId), aws.StringValue(after.ListenerRuleId); before != after { + return create.Error(names.VpcLattice, create.ErrActionCheckingNotRecreated, tfvpclattice.ResNameListenerRule, aws.StringValue(before.ListenerRuleId), errors.New("recreated")) + } + + return nil + } +} + +func testAccListenerRuleConfig_basic(rName, version string) string { + return fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q +} + +resource "aws_vpclattice_listener_rule" "test" { + listener_rule_name = %[1]q + engine_type = "ActiveVpcLattice" + engine_version = %[2]q + host_instance_type = "vpclattice.t2.micro" + security_groups = [aws_security_group.test.id] + authentication_strategy = "simple" + storage_type = "efs" + + logs { + general = true + } + + user { + username = "Test" + password = "TestTest1234" + } +} +`, rName, version) +} diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index 726c2a1fdc4..39d468aedf2 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -30,6 +30,14 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ + { + Factory: ResourceListenerRule, + TypeName: "aws_vpclattice_listener_rule", + Name: "Listener Rule", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, + }, { Factory: ResourceService, TypeName: "aws_vpclattice_service", diff --git a/website/docs/r/vpclattice_listener_rule.html.markdown b/website/docs/r/vpclattice_listener_rule.html.markdown new file mode 100644 index 00000000000..a526f12a47c --- /dev/null +++ b/website/docs/r/vpclattice_listener_rule.html.markdown @@ -0,0 +1,53 @@ +--- +subcategory: "VPC Lattice" +layout: "aws" +page_title: "AWS: aws_vpclattice_listener_rule" +description: |- + Terraform resource for managing an AWS VPC Lattice Listener Rule. +--- + +# Resource: aws_vpclattice_listener_rule + +Terraform resource for managing an AWS VPC Lattice Listener Rule. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_vpclattice_listener_rule" "example" { +} +``` + +## Argument Reference + +The following arguments are required: + +* `example_arg` - (Required) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +The following arguments are optional: + +* `optional_arg` - (Optional) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the Listener Rule. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `example_attribute` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `60m`) +* `update` - (Default `180m`) +* `delete` - (Default `90m`) + +## Import + +VPC Lattice Listener Rule can be imported using the `example_id_arg`, e.g., + +``` +$ terraform import aws_vpclattice_listener_rule.example rft-8012925589 +``` From 2299ce9f0e690cfbf54a34c654fbdc48a7ecf8c4 Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Sun, 16 Apr 2023 20:42:33 +0100 Subject: [PATCH 02/15] Finished Expand finishign flatteners --- internal/service/vpclattice/listener_rule.go | 563 ++++++++++++++----- 1 file changed, 436 insertions(+), 127 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 5962226261e..86c9b8245b1 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -3,7 +3,6 @@ package vpclattice import ( "context" "errors" - "fmt" "log" "time" @@ -57,7 +56,7 @@ func ResourceListenerRule() *schema.Resource { "fixed_response": { Type: schema.TypeList, MaxItems: 1, - Required: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "status_code": { @@ -71,13 +70,15 @@ func ResourceListenerRule() *schema.Resource { }, "forward": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "target_group": { + "target_groups": { Type: schema.TypeList, Required: true, + MinItems: 1, + MaxItems: 2, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "target_group_identifier": { @@ -108,13 +109,20 @@ func ResourceListenerRule() *schema.Resource { Schema: map[string]*schema.Schema{ "http_match": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "headers_match": { + "method": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "headers_matches": { Type: schema.TypeList, - Required: true, + Optional: true, + MinItems: 1, + MaxItems: 5, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "case_sensitive": { @@ -123,21 +131,21 @@ func ResourceListenerRule() *schema.Resource { }, "match": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "contains": { Type: schema.TypeString, - Required: true, + Optional: true, }, "exact": { Type: schema.TypeString, - Required: true, + Optional: true, }, "prefix": { Type: schema.TypeString, - Required: true, + Optional: true, }, }, }, @@ -149,13 +157,9 @@ func ResourceListenerRule() *schema.Resource { }, }, }, - "method": { - Type: schema.TypeString, - Computed: true, - }, "path_match": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -165,17 +169,17 @@ func ResourceListenerRule() *schema.Resource { }, "match": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "exact": { Type: schema.TypeString, - Required: true, + Optional: true, }, "prefix": { Type: schema.TypeString, - Required: true, + Optional: true, }, }, }, @@ -211,21 +215,6 @@ func ResourceListenerRule() *schema.Resource { CustomizeDiff: customdiff.All( verify.SetTagsDiff, - func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { - targetGroupType := types.TargetGroupType(d.Get("type").(string)) - - if v, ok := d.GetOk("config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - if targetGroupType == types.TargetGroupTypeLambda { - return fmt.Errorf(`config not supported for type = %q`, targetGroupType) - } - } else { - if targetGroupType != types.TargetGroupTypeLambda { - return fmt.Errorf(`config required for type = %q`, targetGroupType) - } - } - - return nil - }, ), } } @@ -237,27 +226,12 @@ const ( func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() - // TIP: -- 2. Populate a create input structure - in := &vpclattice.CreateListenerInput{ + in := &vpclattice.CreateRuleInput{ Name: aws.String(d.Get("name").(string)), + // ServiceIdentifier: vpclattice.CreateRuleInput.ServiceIdentifier() } - // if v, ok := d.GetOk("max_size"); ok { - // // TIP: Optional fields should be set based on whether or not they are - // // used. - // in.MaxSize = aws.Int64(int64(v.(int))) - // } - - // if v, ok := d.GetOk("complex_argument"); ok && len(v.([]interface{})) > 0 { - // // TIP: Use an expander to assign a complex argument. - // in.ComplexArguments = expandComplexArguments(v.([]interface{})) - // } - - // TIP: Not all resources support tags and tags don't always make sense. If - // your resource doesn't need tags, you can remove the tags lines here and - // below. Many resources do include tags so this a reminder to include them - // where possible. defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) @@ -604,12 +578,6 @@ func statusListenerRule(ctx context.Context, conn *vpclattice.Client, id string) } } -// TIP: ==== FINDERS ==== -// The find function is not strictly necessary. You could do the API -// request from the status function. However, we have found that find often -// comes in handy in other places besides the status function. As a result, it -// is good practice to define it separately. - func findListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.ListenerRule, error) { in := &vpclattice.GetListenerRuleInput{ Id: aws.String(id), @@ -633,124 +601,465 @@ func findListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id strin return out.ListenerRule, nil } +func flattenRuleAction(apiObject types.RuleAction) map[string]interface{} { + if apiObject == nil { + return nil + } -// TIP: ==== FLEX ==== -// Flatteners and expanders ("flex" functions) help handle complex data -// types. Flatteners take an API data type and return something you can use in -// a d.Set() call. In other words, flatteners translate from AWS -> Terraform. -// -// On the other hand, expanders take a Terraform data structure and return -// something that you can send to the AWS API. In other words, expanders -// translate from Terraform -> AWS. -// -// See more: -// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ -func flattenComplexArgument(apiObject *vpclattice.ComplexArgument) map[string]interface{} { + tfMap := make(map[string]interface{}) + + if v, ok := apiObject.(*types.RuleActionMemberFixedResponse); ok { + tfMap["fixed_response"] = flattenRuleActionMemberFixedResponse(v) + } + if v, ok := apiObject.(*types.RuleActionMemberForward); ok { + tfMap["forward"] = flattenForwardAction(v) + } + + return tfMap +} + +func flattenRuleActionMemberFixedResponse(apiObject *types.RuleActionMemberFixedResponse) map[string]interface{} { if apiObject == nil { return nil } - m := map[string]interface{}{} + tfMap := map[string]interface{}{} + + if v := apiObject.Value.StatusCode; v != nil { + tfMap["status"] = aws.ToInt32(v) + } + + return tfMap +} - if v := apiObject.SubFieldOne; v != nil { - m["sub_field_one"] = aws.ToString(v) +func flattenForwardAction(apiObject *types.ForwardAction) map[string]interface{} { + if apiObject == nil { + return nil } - if v := apiObject.SubFieldTwo; v != nil { - m["sub_field_two"] = aws.ToString(v) + tfMap := map[string]interface{}{} + + if v := apiObject.TargetGroups; v != nil { + tfMap["forward"] = flattenWeightedTargetGroups(v) } - return m + return tfMap } -// TIP: Often the AWS API will return a slice of structures in response to a -// request for information. Sometimes you will have set criteria (e.g., the ID) -// that means you'll get back a one-length slice. This plural function works -// brilliantly for that situation too. -func flattenComplexArguments(apiObjects []*vpclattice.ComplexArgument) []interface{} { +func flattenWeightedTargetGroups(apiObjects []types.WeightedTargetGroup) []interface{} { if len(apiObjects) == 0 { return nil } - var l []interface{} + var tfList []interface{} for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } + tfList = append(tfList, flattenWeightedTargetGroup(&apiObject)) + } + + return tfList +} + +func flattenWeightedTargetGroup(apiObject *types.WeightedTargetGroup) map[string]interface{} { - l = append(l, flattenComplexArgument(apiObject)) + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.TargetGroupIdentifier; v != nil { + tfMap["target_group_identifier"] = aws.ToString(v) + } + + if v := apiObject.Weight; v != nil { + tfMap["weight"] = aws.ToInt32(v) } - return l + return tfMap } -// TIP: Remember, as mentioned above, expanders take a Terraform data structure -// and return something that you can send to the AWS API. In other words, -// expanders translate from Terraform -> AWS. -// -// See more: -// https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/ -func expandComplexArgument(tfMap map[string]interface{}) *vpclattice.ComplexArgument { - if tfMap == nil { +func flattenHttpMatch(apiObject *types.HttpMatch) map[string]interface{} { + if apiObject == nil { return nil } - a := &vpclattice.ComplexArgument{} + tfMap := map[string]interface{}{} - if v, ok := tfMap["sub_field_one"].(string); ok && v != "" { - a.SubFieldOne = aws.String(v) + if v := apiObject.HeaderMatches; v != nil { + tfMap["headers_matches"] = flattenHeaderMatches(v) } - if v, ok := tfMap["sub_field_two"].(string); ok && v != "" { - a.SubFieldTwo = aws.String(v) + if v := apiObject.PathMatch; v != nil { + tfMap["path_match"] = []interface{}{flattenPathMatch(v)} } - return a + return tfMap } -// TIP: Even when you have a list with max length of 1, this plural function -// works brilliantly. However, if the AWS API takes a structure rather than a -// slice of structures, you will not need it. -func expandComplexArguments(tfList []interface{}) []*vpclattice.ComplexArgument { - // TIP: The AWS API can be picky about whether you send a nil or zero- - // length for an argument that should be cleared. For example, in some - // cases, if you send a nil value, the AWS API interprets that as "make no - // changes" when what you want to say is "remove everything." Sometimes - // using a zero-length list will cause an error. - // - // As a result, here are two options. Usually, option 1, nil, will work as - // expected, clearing the field. But, test going from something to nothing - // to make sure it works. If not, try the second option. +func flattenHeaderMatches(apiObjects []types.HeaderMatch) []interface{} { + if len(apiObjects) == 0 { + return nil + } - // TIP: Option 1: Returning nil for zero-length list - if len(tfList) == 0 { + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfList = append(tfList, flattenHeaderMatch(&apiObject)) + } + + return tfList +} + +func flattenHeaderMatch(apiObject *types.HeaderMatch) map[string]interface{} { + if apiObject == nil { return nil } - var s []*vpclattice.ComplexArgument + tfMap := map[string]interface{}{} - // TIP: Option 2: Return zero-length list for zero-length list. If option 1 does - // not work, after testing going from something to nothing (if that is - // possible), uncomment out the next line and remove option 1. - // - // s := make([]*vpclattice.ComplexArgument, 0) + if v := apiObject.CaseSensitive; v != nil { + tfMap["case_sensitive"] = aws.ToBool(v) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.ToString(v) + } + + if v := apiObject.Match; v != nil { + tfMap["match"] = []interface{}{flattenHeaderMatchType(v.(*types.HeaderMatchType))} + } + + return tfMap +} + +func flattenHeaderMatchType(apiObject types.HeaderMatchType) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v, ok := apiObject.(*types.HeaderMatchTypeMemberExact); ok { + tfMap["exact"] = []interface{}{flattenHeaderMatchTypeMemberExact(v)} + } + + if v, ok := apiObject.(*types.HeaderMatchTypeMemberPrefix); ok { + tfMap["prefix"] = []interface{}{flattenHeaderMatchTypeMemberPrefix(v)} + } + + if v, ok := apiObject.(*types.HeaderMatchTypeMemberContains); ok { + tfMap["prefix"] = []interface{}{flattenHeaderMatchTypeMemberContains(v)} + } + + return tfMap +} + +func flattenHeaderMatchTypeMemberContains(apiObject *types.HeaderMatchTypeMemberContains) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "contains": apiObject.Value, + } + + return tfMap +} + +func flattenHeaderMatchTypeMemberExact(apiObject *types.HeaderMatchTypeMemberExact) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "exact": apiObject.Value, + } + + return tfMap +} + +func flattenHeaderMatchTypeMemberPrefix(apiObject *types.HeaderMatchTypeMemberPrefix) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "prefix": apiObject.Value, + } + + return tfMap +} + +func flattenPathMatch(apiObject *types.PathMatch) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.CaseSensitive; v != nil { + tfMap["case_sensitive"] = aws.ToBool(v) + } + + if v := apiObject.Match; v != nil { + tfMap["match"] = []interface{}{flattenPathMatchType(v)} + } + + return tfMap +} - for _, r := range tfList { - m, ok := r.(map[string]interface{}) +func flattenPathMatchType(apiObject types.PathMatchType) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v, ok := apiObject.(*types.PathMatchTypeMemberExact); ok { + tfMap["exact"] = flattenPathMatchTypeMemberExact(v) + } + + if v, ok := apiObject.(*types.PathMatchTypeMemberPrefix); ok { + tfMap["prefix"] = flattenPathMatchTypeMemberPrefix(v) + } + + return tfMap +} + +func flattenPathMatchTypeMemberExact(apiObject *types.PathMatchTypeMemberExact) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "exact": apiObject.Value, + } + + return tfMap +} + +func flattenPathMatchTypeMemberPrefix(apiObject *types.PathMatchTypeMemberPrefix) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "prefix": apiObject.Value, + } + + return tfMap +} + +func expandRuleAction(tfMap map[string]interface{}) types.RuleAction { + var apiObject types.RuleAction + + if v, ok := tfMap["fixed_response_action"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject = expandFixedResponseAction(v[0].(map[string]interface{})) + } + if v, ok := tfMap["forward_action"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject = expandForwardAction(v[0].(map[string]interface{})) + } + + return apiObject +} + +func expandFixedResponseAction(tfMap map[string]interface{}) *types.RuleActionMemberFixedResponse { + apiObject := &types.RuleActionMemberFixedResponse{} + + if v, ok := tfMap["status"].(int); ok && v != 0 { + apiObject.Value.StatusCode = aws.Int32(int32(v)) + } + + return apiObject +} + +func expandForwardAction(tfMap map[string]interface{}) *types.RuleActionMemberForward { + apiObject := &types.RuleActionMemberForward{} + + if v, ok := tfMap["target_groups"].([]interface{}); ok && len(v) > 0 && v != nil { + apiObject.Value.TargetGroups = expandWeightedTargetGroups(v) + } + + return apiObject +} + +func expandWeightedTargetGroups(tfList []interface{}) []types.WeightedTargetGroup { + if len(tfList) == 0 { + return nil + } + + var apiObjects []types.WeightedTargetGroup + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) if !ok { continue } - a := expandComplexArgument(m) + apiObject := expandWeightedTargetGroup(tfMap) + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandWeightedTargetGroup(tfMap map[string]interface{}) types.WeightedTargetGroup { + apiObject := types.WeightedTargetGroup{} + + if v, ok := tfMap["target_group_identifier"].(string); ok && v != "" { + apiObject.TargetGroupIdentifier = aws.String(v) + } + + if v, ok := tfMap["weight"].(int); ok && v != 0 { + apiObject.Weight = aws.Int32(int32(v)) + } + + return apiObject +} + +func expandRuleMatch(tfMap map[string]interface{}) types.RuleMatch { + apiObject := &types.RuleMatchMemberHttpMatch{} + + if v, ok := tfMap["match"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Value = expandHttpMatch(v[0].(map[string]interface{})) + } + + return apiObject +} + +func expandHttpMatch(tfMap map[string]interface{}) types.HttpMatch { + apiObject := types.HttpMatch{} + + if v, ok := tfMap["header_matches"].([]interface{}); ok && len(v) > 0 && v != nil { + apiObject.HeaderMatches = expandHeaderMatches(v) + } + + if v, ok := tfMap["method"].(string); ok { + apiObject.Method = aws.String(v) + } + + if v, ok := tfMap["matcher"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.PathMatch = expandPathMatch(v[0].(map[string]interface{})) + } + + return apiObject +} + +func expandHeaderMatches(tfList []interface{}) []types.HeaderMatch { + if len(tfList) == 0 { + return nil + } + + var apiObjects []types.HeaderMatch - if a == nil { + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { continue } - s = append(s, a) + apiObject := expandHeaderMatch(tfMap) + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandHeaderMatch(tfMap map[string]interface{}) types.HeaderMatch { + apiObject := types.HeaderMatch{} + + if v, ok := tfMap["case_sensitive"].(bool); ok { + apiObject.CaseSensitive = aws.Bool(v) + } + + if v, ok := tfMap["name"].(string); ok { + apiObject.Name = aws.String(v) + } + + if v, ok := tfMap["match"].([]interface{}); ok && len(v) > 0 { + matchObj := v[0].(map[string]interface{}) + if matchV, ok := matchObj["exact"].(string); ok && matchV != "" { + apiObject.Match = expandHeaderMatchTypeMemberExact(matchObj) + } + if matchV, ok := matchObj["prefix"].(string); ok && matchV != "" { + apiObject.Match = expandHeaderMatchTypeMemberPrefix(matchObj) + } + if matchV, ok := matchObj["contains"].(string); ok && matchV != "" { + apiObject.Match = expandHeaderMatchTypeMemberContains(matchObj) + } + } + + return apiObject +} + +func expandHeaderMatchTypeMemberContains(tfMap map[string]interface{}) types.HeaderMatchType { + apiObject := &types.HeaderMatchTypeMemberContains{} + + if v, ok := tfMap["contains"].(string); ok && v != "" { + apiObject.Value = v + } + return apiObject +} + +func expandHeaderMatchTypeMemberPrefix(tfMap map[string]interface{}) types.HeaderMatchType { + apiObject := &types.HeaderMatchTypeMemberPrefix{} + + if v, ok := tfMap["prefix"].(string); ok && v != "" { + apiObject.Value = v + } + return apiObject +} + +func expandHeaderMatchTypeMemberExact(tfMap map[string]interface{}) types.HeaderMatchType { + apiObject := &types.HeaderMatchTypeMemberExact{} + + if v, ok := tfMap["exact"].(string); ok && v != "" { + apiObject.Value = v } + return apiObject +} - return s +func expandPathMatch(tfMap map[string]interface{}) *types.PathMatch { + apiObject := &types.PathMatch{} + + if v, ok := tfMap["case_sensitive"].(bool); ok { + apiObject.CaseSensitive = aws.Bool(v) + } + + if v, ok := tfMap["match"].([]interface{}); ok && len(v) > 0 { + matchObj := v[0].(map[string]interface{}) + + if matchV, ok := matchObj["exact"].(string); ok && matchV != "" { + apiObject.Match = expandPathMatchTypeMemberExact(matchObj) + } + + if matchV, ok := matchObj["prefix"].(string); ok && matchV != "" { + apiObject.Match = expandPathMatchTypeMemberPrefix(matchObj) + } + } + + return apiObject +} + +func expandPathMatchTypeMemberExact(tfMap map[string]interface{}) types.PathMatchType { + apiObject := &types.PathMatchTypeMemberExact{} + + if v, ok := tfMap["exact"].(string); ok && v != "" { + apiObject.Value = v + } + return apiObject +} + +func expandPathMatchTypeMemberPrefix(tfMap map[string]interface{}) types.PathMatchType { + apiObject := &types.PathMatchTypeMemberPrefix{} + + if v, ok := tfMap["prefix"].(string); ok && v != "" { + apiObject.Value = v + } + return apiObject } From 3d128348d14e1c2721b1ffaf86fef9135b99e382 Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Sun, 16 Apr 2023 20:52:15 +0100 Subject: [PATCH 03/15] finished flatteners --- internal/service/vpclattice/listener_rule.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 86c9b8245b1..57444da64c3 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -601,6 +601,7 @@ func findListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id strin return out.ListenerRule, nil } + func flattenRuleAction(apiObject types.RuleAction) map[string]interface{} { if apiObject == nil { return nil @@ -632,14 +633,14 @@ func flattenRuleActionMemberFixedResponse(apiObject *types.RuleActionMemberFixed return tfMap } -func flattenForwardAction(apiObject *types.ForwardAction) map[string]interface{} { +func flattenForwardAction(apiObject *types.RuleActionMemberForward) map[string]interface{} { if apiObject == nil { return nil } tfMap := map[string]interface{}{} - if v := apiObject.TargetGroups; v != nil { + if v := apiObject.Value.TargetGroups; v != nil { tfMap["forward"] = flattenWeightedTargetGroups(v) } From da24fa2e91cd5e341bf7a25894aca1ce576eaf54 Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Sun, 16 Apr 2023 22:13:20 +0100 Subject: [PATCH 04/15] creating crud --- internal/service/vpclattice/listener_rule.go | 174 +++++++------------ 1 file changed, 62 insertions(+), 112 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 57444da64c3..56ffcb33780 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -11,9 +11,10 @@ import ( "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -209,6 +210,18 @@ func ResourceListenerRule() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "listener_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "service_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, @@ -226,136 +239,65 @@ const ( func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() + name := d.Get("name").(string) in := &vpclattice.CreateRuleInput{ - Name: aws.String(d.Get("name").(string)), - - // ServiceIdentifier: vpclattice.CreateRuleInput.ServiceIdentifier() + ClientToken: aws.String(id.UniqueId()), + Name: aws.String(name), + ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), + ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), + Tags: GetTagsIn(ctx), } - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) - - if len(tags) > 0 { - in.Tags = Tags(tags.IgnoreAWS()) + if v, ok := d.GetOk("action"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.Action = expandRuleAction(v.([]interface{})[0].(map[string]interface{})) } - // TIP: -- 3. Call the AWS create function - out, err := conn.CreateListenerRule(ctx, in) - if err != nil { - // TIP: Since d.SetId() has not been called yet, you cannot use d.Id() - // in error messages at this point. - return create.DiagError(names.VpcLattice, create.ErrActionCreating, ResNameListenerRule, d.Get("name").(string), err) + if v, ok := d.GetOk("match"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.Match = expandRuleMatch(v.([]interface{})[0].(map[string]interface{})) } - if out == nil || out.ListenerRule == nil { - return create.DiagError(names.VpcLattice, create.ErrActionCreating, ResNameListenerRule, d.Get("name").(string), errors.New("empty output")) + out, err := conn.CreateRule(ctx, in) + + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameService, name, err) } - // TIP: -- 4. Set the minimum arguments and/or attributes for the Read function to - // work. - d.SetId(aws.ToString(out.ListenerRule.ListenerRuleID)) + d.SetId(aws.ToString(out.Id)) - // TIP: -- 5. Use a waiter to wait for create to complete - if _, err := waitListenerRuleCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionWaitingForCreation, ResNameListenerRule, d.Id(), err) + if _, err := waitTargetGroupCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionWaitingForCreation, ResNameTargetGroup, d.Id(), err) } - // TIP: -- 6. Call the Read function in the Create return - return resourceListenerRuleRead(ctx, d, meta) + return resourceTargetGroupRead(ctx, d, meta) } func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - // TIP: ==== RESOURCE READ ==== - // Generally, the Read function should do the following things. Make - // sure there is a good reason if you don't do one of these. - // - // 1. Get a client connection to the relevant service - // 2. Get the resource from AWS - // 3. Set ID to empty where resource is not new and not found - // 4. Set the arguments and attributes - // 5. Set the tags - // 6. Return nil - - // TIP: -- 1. Get a client connection to the relevant service - conn := meta.(*conns.AWSClient).VpcLatticeClient() + conn := meta.(*conns.AWSClient).VPCLatticeClient() - // TIP: -- 2. Get the resource from AWS using an API Get, List, or Describe- - // type function, or, better yet, using a finder. - out, err := findListenerRuleByID(ctx, conn, d.Id()) + out, err := FindListenerRuleByID(ctx, conn, d.Id()) - // TIP: -- 3. Set ID to empty where resource is not new and not found if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] VpcLattice ListenerRule (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] VpcLattice Listener Rule (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionReading, ResNameListenerRule, d.Id(), err) + return create.DiagError(names.VPCLattice, create.ErrActionReading, ResNameListenerRule, d.Id(), err) } - // TIP: -- 4. Set the arguments and attributes - // - // For simple data types (i.e., schema.TypeString, schema.TypeBool, - // schema.TypeInt, and schema.TypeFloat), a simple Set call (e.g., - // d.Set("arn", out.Arn) is sufficient. No error or nil checking is - // necessary. - // - // However, there are some situations where more handling is needed. - // a. Complex data types (e.g., schema.TypeList, schema.TypeSet) - // b. Where errorneous diffs occur. For example, a schema.TypeString may be - // a JSON. AWS may return the JSON in a slightly different order but it - // is equivalent to what is already set. In that case, you may check if - // it is equivalent before setting the different JSON. d.Set("arn", out.Arn) - d.Set("name", out.Name) - // TIP: Setting a complex type. - // For more information, see: - // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#data-handling-and-conversion - // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#flatten-functions-for-blocks - // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#root-typeset-of-resource-and-aws-list-of-structure - if err := d.Set("complex_argument", flattenComplexArguments(out.ComplexArguments)); err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) + if err := d.Set("action", []interface{}{flattenRuleAction(out.Action)}); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) } - // TIP: Setting a JSON string to avoid errorneous diffs. - p, err := verify.SecondJSONUnlessEquivalent(d.Get("policy").(string), aws.ToString(out.Policy)) - if err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) - } - - p, err = structure.NormalizeJsonString(p) - if err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) - } - - d.Set("policy", p) - - // TIP: -- 5. Set the tags - // - // TIP: Not all resources support tags and tags don't always make sense. If - // your resource doesn't need tags, you can remove the tags lines here and - // below. Many resources do include tags so this a reminder to include them - // where possible. - tags, err := ListTags(ctx, conn, d.Id()) - if err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionReading, ResNameListenerRule, d.Id(), err) - } - - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - - if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) + if err := d.Set("match", []interface{}{flattenRuleMatch(out.Match)}); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) } - if err := d.Set("tags_all", tags.Map()); err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) - } + d.Set("name", out.Name) - // TIP: -- 6. Return nil return nil } @@ -470,10 +412,6 @@ func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, met return nil } -// TIP: ==== STATUS CONSTANTS ==== -// Create constants for states and statuses if the service does not -// already have suitable constants. We prefer that you use the constants -// provided in the service if available (e.g., amp.WorkspaceStatusCodeActive). const ( statusChangePending = "Pending" statusDeleting = "Deleting" @@ -518,7 +456,7 @@ func waitListenerRuleCreated(ctx context.Context, conn *vpclattice.Client, id st // the update has been fully realized. Other times, you can check to see if a // key resource argument is updated to a new value or not. -func waitListenerRuleUpdated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { +func waitListenerRuleUpdated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetRuleOutput, error) { stateConf := &resource.StateChangeConf{ Pending: []string{statusChangePending}, Target: []string{statusUpdated}, @@ -565,7 +503,7 @@ func waitListenerRuleDeleted(ctx context.Context, conn *vpclattice.Client, id st func statusListenerRule(ctx context.Context, conn *vpclattice.Client, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - out, err := findListenerRuleByID(ctx, conn, id) + out, err := FindListenerRuleByID(ctx, conn, id) if tfresource.NotFound(err) { return nil, "", nil } @@ -578,15 +516,15 @@ func statusListenerRule(ctx context.Context, conn *vpclattice.Client, id string) } } -func findListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.ListenerRule, error) { - in := &vpclattice.GetListenerRuleInput{ - Id: aws.String(id), +func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.GetRuleOutput, error) { + in := &vpclattice.GetRuleInput{ + RuleIdentifier: aws.String(id), } - out, err := conn.GetListenerRule(ctx, in) + out, err := conn.GetRule(ctx, in) if err != nil { var nfe *types.ResourceNotFoundException if errors.As(err, &nfe) { - return nil, &resource.NotFoundError{ + return nil, &retry.NotFoundError{ LastError: err, LastRequest: in, } @@ -595,11 +533,11 @@ func findListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id strin return nil, err } - if out == nil || out.ListenerRule == nil { + if out == nil || out.Id == nil { return nil, tfresource.NewEmptyResultError(in) } - return out.ListenerRule, nil + return out, nil } func flattenRuleAction(apiObject types.RuleAction) map[string]interface{} { @@ -679,7 +617,19 @@ func flattenWeightedTargetGroup(apiObject *types.WeightedTargetGroup) map[string return tfMap } +func flattenRuleMatch(apiObject types.RuleMatch) map[string]interface{} { + if apiObject == nil { + return nil + } + tfMap := make(map[string]interface{}) + + if v, ok := apiObject.(*types.HttpMatch); ok { + tfMap["http_match"] = flattenHttpMatch(v) + } + + return tfMap +} func flattenHttpMatch(apiObject *types.HttpMatch) map[string]interface{} { if apiObject == nil { return nil From fd37331998299752c16e1fbe420a0996fb927702 Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Mon, 17 Apr 2023 12:47:10 +0100 Subject: [PATCH 05/15] starting tests --- internal/service/vpclattice/listener_rule.go | 410 +++++++----------- .../service/vpclattice/listener_rule_test.go | 291 +++---------- 2 files changed, 215 insertions(+), 486 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 56ffcb33780..052d7ecd43d 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -30,7 +29,7 @@ func ResourceListenerRule() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceListenerRuleCreate, ReadWithoutTimeout: resourceListenerRuleRead, - UpdateWithoutTimeout: resourceListenerRuleUpdate, + // UpdateWithoutTimeout: resourceListenerRuleUpdate, DeleteWithoutTimeout: resourceListenerRuleDelete, Importer: &schema.ResourceImporter{ @@ -206,10 +205,6 @@ func ResourceListenerRule() *schema.Resource { Computed: true, ForceNew: false, }, - "status": { - Type: schema.TypeString, - Computed: true, - }, "listener_identifier": { Type: schema.TypeString, @@ -247,7 +242,6 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), Tags: GetTagsIn(ctx), } - if v, ok := d.GetOk("action"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { in.Action = expandRuleAction(v.([]interface{})[0].(map[string]interface{})) } @@ -274,7 +268,7 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() - out, err := FindListenerRuleByID(ctx, conn, d.Id()) + out, err := FindListenerRuleByID(ctx, conn, d.Id(), d.Get("listener_identifier").(string), d.Get("service_identifier").(string)) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] VpcLattice Listener Rule (%s) not found, removing from state", d.Id()) @@ -301,224 +295,148 @@ func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta return nil } -func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - // TIP: ==== RESOURCE UPDATE ==== - // Not all resources have Update functions. There are a few reasons: - // a. The AWS API does not support changing a resource - // b. All arguments have ForceNew: true, set - // c. The AWS API uses a create call to modify an existing resource - // - // In the cases of a. and b., the main resource function will not have a - // UpdateWithoutTimeout defined. In the case of c., Update and Create are - // the same. - // - // The rest of the time, there should be an Update function and it should - // do the following things. Make sure there is a good reason if you don't - // do one of these. - // - // 1. Get a client connection to the relevant service - // 2. Populate a modify input structure and check for changes - // 3. Call the AWS modify/update function - // 4. Use a waiter to wait for update to complete - // 5. Call the Read function in the Update return - - // TIP: -- 1. Get a client connection to the relevant service - conn := meta.(*conns.AWSClient).VpcLatticeClient() - - // TIP: -- 2. Populate a modify input structure and check for changes - // - // When creating the input structure, only include mandatory fields. Other - // fields are set as needed. You can use a flag, such as update below, to - // determine if a certain portion of arguments have been changed and - // whether to call the AWS update function. - update := false - - in := &vpclattice.UpdateListenerRuleInput{ - Id: aws.String(d.Id()), - } - - if d.HasChanges("an_argument") { - in.AnArgument = aws.String(d.Get("an_argument").(string)) - update = true - } - - if !update { - // TIP: If update doesn't do anything at all, which is rare, you can - // return nil. Otherwise, return a read call, as below. - return nil - } +// func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - // TIP: -- 3. Call the AWS modify/update function - log.Printf("[DEBUG] Updating VpcLattice ListenerRule (%s): %#v", d.Id(), in) - out, err := conn.UpdateListenerRule(ctx, in) - if err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionUpdating, ResNameListenerRule, d.Id(), err) - } +// conn := meta.(*conns.AWSClient).VpcLatticeClient() - // TIP: -- 4. Use a waiter to wait for update to complete - if _, err := waitListenerRuleUpdated(ctx, conn, aws.ToString(out.OperationId), d.Timeout(schema.TimeoutUpdate)); err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionWaitingForUpdate, ResNameListenerRule, d.Id(), err) - } +// update := false - // TIP: -- 5. Call the Read function in the Update return - return resourceListenerRuleRead(ctx, d, meta) -} +// in := &vpclattice.BatchUpdateRuleInput{ +// Id: aws.String(d.Id()), +// } + +// if d.HasChanges("an_argument") { +// in.AnArgument = aws.String(d.Get("an_argument").(string)) +// update = true +// } + +// if !update { +// // TIP: If update doesn't do anything at all, which is rare, you can +// // return nil. Otherwise, return a read call, as below. +// return nil +// } + +// // TIP: -- 3. Call the AWS modify/update function +// log.Printf("[DEBUG] Updating VpcLattice ListenerRule (%s): %#v", d.Id(), in) +// out, err := conn.UpdateListenerRule(ctx, in) +// if err != nil { +// return create.DiagError(names.VpcLattice, create.ErrActionUpdating, ResNameListenerRule, d.Id(), err) +// } + +// // TIP: -- 4. Use a waiter to wait for update to complete +// if _, err := waitListenerRuleUpdated(ctx, conn, aws.ToString(out.OperationId), d.Timeout(schema.TimeoutUpdate)); err != nil { +// return create.DiagError(names.VpcLattice, create.ErrActionWaitingForUpdate, ResNameListenerRule, d.Id(), err) +// } + +// // TIP: -- 5. Call the Read function in the Update return +// return resourceListenerRuleRead(ctx, d, meta) +// } func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - // TIP: ==== RESOURCE DELETE ==== - // Most resources have Delete functions. There are rare situations - // where you might not need a delete: - // a. The AWS API does not provide a way to delete the resource - // b. The point of your resource is to perform an action (e.g., reboot a - // server) and deleting serves no purpose. - // - // The Delete function should do the following things. Make sure there - // is a good reason if you don't do one of these. - // - // 1. Get a client connection to the relevant service - // 2. Populate a delete input structure - // 3. Call the AWS delete function - // 4. Use a waiter to wait for delete to complete - // 5. Return nil - - // TIP: -- 1. Get a client connection to the relevant service - conn := meta.(*conns.AWSClient).VpcLatticeClient() - - // TIP: -- 2. Populate a delete input structure - log.Printf("[INFO] Deleting VpcLattice ListenerRule %s", d.Id()) - - // TIP: -- 3. Call the AWS delete function - _, err := conn.DeleteListenerRule(ctx, &vpclattice.DeleteListenerRuleInput{ - Id: aws.String(d.Id()), + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + log.Printf("[INFO] Deleting VpcLattice ListeningRule: %s", d.Id()) + _, err := conn.DeleteRule(ctx, &vpclattice.DeleteRuleInput{ + RuleIdentifier: aws.String(d.Id()), + ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), + ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), }) - // TIP: On rare occassions, the API returns a not found error after deleting a - // resource. If that happens, we don't want it to show up as an error. if err != nil { var nfe *types.ResourceNotFoundException if errors.As(err, &nfe) { return nil } - return create.DiagError(names.VpcLattice, create.ErrActionDeleting, ResNameListenerRule, d.Id(), err) + return create.DiagError(names.VPCLattice, create.ErrActionDeleting, ResNameTargetGroup, d.Id(), err) } - // TIP: -- 4. Use a waiter to wait for delete to complete - if _, err := waitListenerRuleDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return create.DiagError(names.VpcLattice, create.ErrActionWaitingForDeletion, ResNameListenerRule, d.Id(), err) + if _, err := waitTargetGroupDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionWaitingForDeletion, ResNameTargetGroup, d.Id(), err) } - // TIP: -- 5. Return nil return nil } -const ( - statusChangePending = "Pending" - statusDeleting = "Deleting" - statusNormal = "Normal" - statusUpdated = "Updated" -) - -// TIP: ==== WAITERS ==== -// Some resources of some services have waiters provided by the AWS API. -// Unless they do not work properly, use them rather than defining new ones -// here. -// -// Sometimes we define the wait, status, and find functions in separate -// files, wait.go, status.go, and find.go. Follow the pattern set out in the -// service and define these where it makes the most sense. -// -// If these functions are used in the _test.go file, they will need to be -// exported (i.e., capitalized). -// -// You will need to adjust the parameters and names to fit the service. - -func waitListenerRuleCreated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{}, - Target: []string{statusNormal}, - Refresh: statusListenerRule(ctx, conn, id), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { - return out, err - } - - return nil, err -} - -// TIP: It is easier to determine whether a resource is updated for some -// resources than others. The best case is a status flag that tells you when -// the update has been fully realized. Other times, you can check to see if a -// key resource argument is updated to a new value or not. - -func waitListenerRuleUpdated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetRuleOutput, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{statusChangePending}, - Target: []string{statusUpdated}, - Refresh: statusListenerRule(ctx, conn, id), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { - return out, err - } - - return nil, err -} - -// TIP: A deleted waiter is almost like a backwards created waiter. There may -// be additional pending states, however. - -func waitListenerRuleDeleted(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{statusDeleting, statusNormal}, - Target: []string{}, - Refresh: statusListenerRule(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { - return out, err - } - - return nil, err -} - -// TIP: ==== STATUS ==== -// The status function can return an actual status when that field is -// available from the API (e.g., out.Status). Otherwise, you can use custom -// statuses to communicate the states of the resource. -// -// Waiters consume the values returned by status functions. Design status so -// that it can be reused by a create, update, and delete waiter, if possible. - -func statusListenerRule(ctx context.Context, conn *vpclattice.Client, id string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := FindListenerRuleByID(ctx, conn, id) - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return out, aws.ToString(out.Status), nil - } -} - -func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.GetRuleOutput, error) { +// const ( +// statusChangePending = "Pending" +// statusDeleting = "Deleting" +// statusNormal = "Normal" +// statusUpdated = "Updated" +// ) + +// func waitListenerRuleCreated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { +// stateConf := &resource.StateChangeConf{ +// Pending: []string{}, +// Target: []string{statusNormal}, +// Refresh: statusListenerRule(ctx, conn, id), +// Timeout: timeout, +// NotFoundChecks: 20, +// ContinuousTargetOccurence: 2, +// } + +// outputRaw, err := stateConf.WaitForStateContext(ctx) +// if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { +// return out, err +// } + +// return nil, err +// } + +// func waitListenerRuleUpdated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetRuleOutput, error) { +// stateConf := &resource.StateChangeConf{ +// Pending: []string{statusChangePending}, +// Target: []string{statusUpdated}, +// Refresh: statusListenerRule(ctx, conn, id), +// Timeout: timeout, +// NotFoundChecks: 20, +// ContinuousTargetOccurence: 2, +// } + +// outputRaw, err := stateConf.WaitForStateContext(ctx) +// if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { +// return out, err +// } + +// return nil, err +// } + +// func waitListenerRuleDeleted(ctx context.Context, conn *vpclattice.Client, id, listenerIdentifier, serviceIdentifier string, timeout time.Duration) (*vpclattice.GetRuleOutput, error) { +// stateConf := &resource.StateChangeConf{ +// Pending: []string{statusDeleting, statusNormal}, +// Target: []string{}, +// Refresh: statusListenerRule(ctx, conn, id, listenerIdentifier, serviceIdentifier), +// Timeout: timeout, +// } + +// outputRaw, err := stateConf.WaitForStateContext(ctx) +// if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { +// return out, err +// } + +// return nil, err +// } + +// func statusListenerRule(ctx context.Context, conn *vpclattice.Client, id, listenerIdentifier, serviceIdentifier string) resource.StateRefreshFunc { +// return func() (interface{}, string, error) { +// out, err := FindListenerRuleByID(ctx, conn, id, listenerIdentifier, serviceIdentifier) +// if tfresource.NotFound(err) { +// return nil, "", nil +// } + +// if err != nil { +// return nil, "", err +// } + +// return out, aws.ToString(out.), nil +// } +// } + +func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id, listenerIdentifier, serviceIdentifier string) (*vpclattice.GetRuleOutput, error) { in := &vpclattice.GetRuleInput{ - RuleIdentifier: aws.String(id), + RuleIdentifier: aws.String(id), + ListenerIdentifier: aws.String(listenerIdentifier), + ServiceIdentifier: aws.String(serviceIdentifier), } out, err := conn.GetRule(ctx, in) if err != nil { @@ -532,7 +450,6 @@ func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id strin return nil, err } - if out == nil || out.Id == nil { return nil, tfresource.NewEmptyResultError(in) } @@ -565,7 +482,7 @@ func flattenRuleActionMemberFixedResponse(apiObject *types.RuleActionMemberFixed tfMap := map[string]interface{}{} if v := apiObject.Value.StatusCode; v != nil { - tfMap["status"] = aws.ToInt32(v) + tfMap["status_code"] = aws.ToInt32(v) } return tfMap @@ -617,6 +534,7 @@ func flattenWeightedTargetGroup(apiObject *types.WeightedTargetGroup) map[string return tfMap } + func flattenRuleMatch(apiObject types.RuleMatch) map[string]interface{} { if apiObject == nil { return nil @@ -624,12 +542,13 @@ func flattenRuleMatch(apiObject types.RuleMatch) map[string]interface{} { tfMap := make(map[string]interface{}) - if v, ok := apiObject.(*types.HttpMatch); ok { - tfMap["http_match"] = flattenHttpMatch(v) + if v, ok := apiObject.(*types.RuleMatchMemberHttpMatch); ok { + tfMap["http_match"] = flattenHttpMatch(&v.Value) } return tfMap } + func flattenHttpMatch(apiObject *types.HttpMatch) map[string]interface{} { if apiObject == nil { return nil @@ -637,8 +556,12 @@ func flattenHttpMatch(apiObject *types.HttpMatch) map[string]interface{} { tfMap := map[string]interface{}{} + if v := apiObject.Method; v != nil { + tfMap["method"] = aws.ToString(v) + } + if v := apiObject.HeaderMatches; v != nil { - tfMap["headers_matches"] = flattenHeaderMatches(v) + tfMap["headers_matches"] = []interface{}{flattenHeaderMatches(v)} } if v := apiObject.PathMatch; v != nil { @@ -678,33 +601,41 @@ func flattenHeaderMatch(apiObject *types.HeaderMatch) map[string]interface{} { } if v := apiObject.Match; v != nil { - tfMap["match"] = []interface{}{flattenHeaderMatchType(v.(*types.HeaderMatchType))} + if exact, ok := v.(*types.HeaderMatchTypeMemberExact); ok { + tfMap["exact"] = flattenHeaderMatchTypeMemberExact(exact) + } + if prefix, ok := v.(*types.HeaderMatchTypeMemberPrefix); ok { + tfMap["prefix"] = flattenHeaderMatchTypeMemberPrefix(prefix) + } + if contains, ok := v.(*types.HeaderMatchTypeMemberContains); ok { + tfMap["contains"] = flattenHeaderMatchTypeMemberContains(contains) + } } return tfMap } -func flattenHeaderMatchType(apiObject types.HeaderMatchType) map[string]interface{} { - if apiObject == nil { - return nil - } +// func flattenHeaderMatchType(apiObject types.HeaderMatchType) map[string]interface{} { +// if apiObject == nil { +// return nil +// } - tfMap := make(map[string]interface{}) +// tfMap := make(map[string]interface{}) - if v, ok := apiObject.(*types.HeaderMatchTypeMemberExact); ok { - tfMap["exact"] = []interface{}{flattenHeaderMatchTypeMemberExact(v)} - } +// if v, ok := apiObject.(*types.HeaderMatchTypeMemberExact); ok { +// tfMap["exact"] = []interface{}{flattenHeaderMatchTypeMemberExact(v)} +// } - if v, ok := apiObject.(*types.HeaderMatchTypeMemberPrefix); ok { - tfMap["prefix"] = []interface{}{flattenHeaderMatchTypeMemberPrefix(v)} - } +// if v, ok := apiObject.(*types.HeaderMatchTypeMemberPrefix); ok { +// tfMap["prefix"] = []interface{}{flattenHeaderMatchTypeMemberPrefix(v)} +// } - if v, ok := apiObject.(*types.HeaderMatchTypeMemberContains); ok { - tfMap["prefix"] = []interface{}{flattenHeaderMatchTypeMemberContains(v)} - } +// if v, ok := apiObject.(*types.HeaderMatchTypeMemberContains); ok { +// tfMap["contains"] = []interface{}{flattenHeaderMatchTypeMemberContains(v)} +// } - return tfMap -} +// return tfMap +// } func flattenHeaderMatchTypeMemberContains(apiObject *types.HeaderMatchTypeMemberContains) map[string]interface{} { if apiObject == nil { @@ -754,25 +685,12 @@ func flattenPathMatch(apiObject *types.PathMatch) map[string]interface{} { } if v := apiObject.Match; v != nil { - tfMap["match"] = []interface{}{flattenPathMatchType(v)} - } - - return tfMap -} - -func flattenPathMatchType(apiObject types.PathMatchType) map[string]interface{} { - if apiObject == nil { - return nil - } - - tfMap := make(map[string]interface{}) - - if v, ok := apiObject.(*types.PathMatchTypeMemberExact); ok { - tfMap["exact"] = flattenPathMatchTypeMemberExact(v) - } - - if v, ok := apiObject.(*types.PathMatchTypeMemberPrefix); ok { - tfMap["prefix"] = flattenPathMatchTypeMemberPrefix(v) + if exact, ok := v.(*types.PathMatchTypeMemberExact); ok { + tfMap["exact"] = flattenPathMatchTypeMemberExact(exact) + } + if prefix, ok := v.(*types.PathMatchTypeMemberPrefix); ok { + tfMap["prefix"] = flattenPathMatchTypeMemberPrefix(prefix) + } } return tfMap diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go index 110baa2d52c..5bf05d68c0f 100644 --- a/internal/service/vpclattice/listener_rule_test.go +++ b/internal/service/vpclattice/listener_rule_test.go @@ -1,39 +1,6 @@ package vpclattice_test -// **PLEASE DELETE THIS AND ALL TIP COMMENTS BEFORE SUBMITTING A PR FOR REVIEW!** -// -// TIP: ==== INTRODUCTION ==== -// Thank you for trying the skaff tool! -// -// You have opted to include these helpful comments. They all include "TIP:" -// to help you find and remove them when you're done with them. -// -// While some aspects of this file are customized to your input, the -// scaffold tool does *not* look at the AWS API and ensure it has correct -// function, structure, and variable names. It makes guesses based on -// commonalities. You will need to make significant adjustments. -// -// In other words, as generated, this is a rough outline of the work you will -// need to do. If something doesn't make sense for your situation, get rid of -// it. -// -// Remember to register this new resource in the provider -// (internal/provider/provider.go) once you finish. Otherwise, Terraform won't -// know about it. - import ( - // TIP: ==== IMPORTS ==== - // This is a common set of imports but not customized to your code since - // your code hasn't been written yet. Make sure you, your IDE, or - // goimports -w fixes these imports. - // - // The provider linter wants your imports to be in two groups: first, - // standard library (i.e., "fmt" or "strings"), second, everything else. - // - // Also, AWS Go SDK v2 may handle nested structures differently than v1, - // using the services/vpclattice/types package. If so, you'll - // need to import types and reference the nested types, e.g., as - // types.. "context" "errors" "fmt" @@ -49,262 +16,106 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" - - // TIP: You will often need to import the package that this test file lives - // in. Since it is in the "test" context, it must import the package to use - // any normal context constants, variables, or functions. tfvpclattice "github.com/hashicorp/terraform-provider-aws/internal/service/vpclattice" "github.com/hashicorp/terraform-provider-aws/names" ) -// TIP: File Structure. The basic outline for all test files should be as -// follows. Improve this resource's maintainability by following this -// outline. -// -// 1. Package declaration (add "_test" since this is a test file) -// 2. Imports -// 3. Unit tests -// 4. Basic test -// 5. Disappears test -// 6. All the other tests -// 7. Helper functions (exists, destroy, check, etc.) -// 8. Functions that return Terraform configurations - -// TIP: ==== UNIT TESTS ==== -// This is an example of a unit test. Its name is not prefixed with -// "TestAcc" like an acceptance test. -// -// Unlike acceptance tests, unit tests do not access AWS and are focused on a -// function (or method). Because of this, they are quick and cheap to run. -// -// In designing a resource's implementation, isolate complex bits from AWS bits -// so that they can be tested through a unit test. We encourage more unit tests -// in the provider. -// -// Cut and dry functions using well-used patterns, like typical flatteners and -// expanders, don't need unit testing. However, if they are complex or -// intricate, they should be unit tested. -func TestListenerRuleExampleUnitTest(t *testing.T) { - testCases := []struct { - TestName string - Input string - Expected string - Error bool - }{ - { - TestName: "empty", - Input: "", - Expected: "", - Error: true, - }, - { - TestName: "descriptive name", - Input: "some input", - Expected: "some output", - Error: false, - }, - { - TestName: "another descriptive name", - Input: "more input", - Expected: "more output", - Error: false, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.TestName, func(t *testing.T) { - got, err := tfvpclattice.FunctionFromResource(testCase.Input) - - if err != nil && !testCase.Error { - t.Errorf("got error (%s), expected no error", err) - } - - if err == nil && testCase.Error { - t.Errorf("got (%s) and no error, expected error", got) - } - - if got != testCase.Expected { - t.Errorf("got %s, expected %s", got, testCase.Expected) - } - }) - } -} - -// TIP: ==== ACCEPTANCE TESTS ==== -// This is an example of a basic acceptance test. This should test as much of -// standard functionality of the resource as possible, and test importing, if -// applicable. We prefix its name with "TestAcc", the service, and the -// resource name. -// -// Acceptance test access AWS and cost money to run. -func TestAccVpcLatticeListenerRule_basic(t *testing.T) { +func TestAccVPCLatticeListenerRule_basic(t *testing.T) { ctx := acctest.Context(t) - // TIP: This is a long-running test guard for tests that run longer than - // 300s (5 min) generally. - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var listenerrule vpclattice.DescribeListenerRuleResponse + var targetGroup vpclattice.GetTargetGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_vpclattice_listener_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckPartitionHasService(t, names.VpcLatticeEndpointID) + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) testAccPreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.VpcLatticeEndpointID), + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckListenerRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccListenerRuleConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckListenerRuleExists(ctx, resourceName, &listenerrule), - resource.TestCheckResourceAttr(resourceName, "auto_minor_version_upgrade", "false"), - resource.TestCheckResourceAttrSet(resourceName, "maintenance_window_start_time.0.day_of_week"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "user.*", map[string]string{ - "console_access": "false", - "groups.#": "0", - "username": "Test", - "password": "TestTest1234", - }), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpclattice", regexp.MustCompile(`listenerrule:+.`)), + Config: testAccTargetGroupConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("targetgroup/.+$")), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } -func TestAccVpcLatticeListenerRule_disappears(t *testing.T) { - ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") +func testAccListeningRule_basic(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` +resource "aws_vpclattice_listener_rule" "test" { + listener_identifier = "arn:aws:vpc-lattice:eu-west-1:926562225508:service/svc-05cad7dcf6ee78d45/listener/listener-061ead87f8dcf282c" + service_identifier = "arn:aws:vpc-lattice:eu-west-1:926562225508:service/svc-05cad7dcf6ee78d45" + action { + target_group_arn = "arn:aws:vpc-lattice:eu-west-1:926562225508:targetgroup/tg-00153386728e69d10" + } + match { + path_match = "/" } - - var listenerrule vpclattice.DescribeListenerRuleResponse - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_vpclattice_listener_rule.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckPartitionHasService(t, names.VpcLatticeEndpointID) - testAccPreCheck(t) - }, - ErrorCheck: acctest.ErrorCheck(t, names.VpcLatticeEndpointID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckListenerRuleDestroy, - Steps: []resource.TestStep{ - { - Config: testAccListenerRuleConfig_basic(rName, testAccListenerRuleVersionNewer), - Check: resource.ComposeTestCheckFunc( - testAccCheckListenerRuleExists(resourceName, &listenerrule), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfvpclattice.ResourceListenerRule(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) } - -func testAccCheckListenerRuleDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).VpcLatticeClient() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_vpclattice_listener_rule" { - continue - } - - input := &vpclattice.DescribeListenerRuleInput{ - ListenerRuleId: aws.String(rs.Primary.ID), - } - _, err := conn.DescribeListenerRule(ctx, &vpclattice.DescribeListenerRuleInput{ - ListenerRuleId: aws.String(rs.Primary.ID), - }) - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil - } - return err - } - - return create.Error(names.VpcLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameListenerRule, rs.Primary.ID, errors.New("not destroyed")) - } - - return nil - } +`, rName)) } -func testAccCheckListenerRuleExists(ctx context.Context, name string, listenerrule *vpclattice.DescribeListenerRuleResponse) resource.TestCheckFunc { +func testAccCheckListeningRuleExists(ctx context.Context, name string, targetGroup *vpclattice.GetRuleOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { - return create.Error(names.VpcLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not found")) + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not found")) } if rs.Primary.ID == "" { - return create.Error(names.VpcLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not set")) + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not set")) } - conn := acctest.Provider.Meta().(*conns.AWSClient).VpcLatticeClient() - resp, err := conn.DescribeListenerRule(ctx, &vpclattice.DescribeListenerRuleInput{ - ListenerRuleId: aws.String(rs.Primary.ID), + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() + resp, err := conn.GetRule(ctx, &vpclattice.GetRuleInput{ + ListenerIdentifier: aws.String(rs.Primary.ID), }) if err != nil { - return create.Error(names.VpcLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, rs.Primary.ID, err) + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, rs.Primary.ID, err) } - *listenerrule = *resp + *targetGroup = *resp return nil } } -func testAccCheckListenerRuleNotRecreated(before, after *vpclattice.DescribeListenerRuleResponse) resource.TestCheckFunc { +func testAccCheckListenerRuleDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - if before, after := aws.StringValue(before.ListenerRuleId), aws.StringValue(after.ListenerRuleId); before != after { - return create.Error(names.VpcLattice, create.ErrActionCheckingNotRecreated, tfvpclattice.ResNameListenerRule, aws.StringValue(before.ListenerRuleId), errors.New("recreated")) - } + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() - return nil - } -} - -func testAccListenerRuleConfig_basic(rName, version string) string { - return fmt.Sprintf(` -resource "aws_security_group" "test" { - name = %[1]q -} + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpclattice_listener_rule" { + continue + } -resource "aws_vpclattice_listener_rule" "test" { - listener_rule_name = %[1]q - engine_type = "ActiveVpcLattice" - engine_version = %[2]q - host_instance_type = "vpclattice.t2.micro" - security_groups = [aws_security_group.test.id] - authentication_strategy = "simple" - storage_type = "efs" + _, err := conn.GetRule(ctx, &vpclattice.GetRuleInput{ + ListenerIdentifier: aws.String(rs.Primary.ID), + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } - logs { - general = true - } + return create.Error(names.VPCLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameListenerRule, rs.Primary.ID, errors.New("not destroyed")) + } - user { - username = "Test" - password = "TestTest1234" - } -} -`, rName, version) + return nil + } } From dd87fe08611780c78c6fd7fdb7045f8fdc66418f Mon Sep 17 00:00:00 2001 From: mkandyli Date: Mon, 17 Apr 2023 19:22:19 +0100 Subject: [PATCH 06/15] starting tests --- internal/service/vpclattice/listener_rule.go | 56 ++++++++------------ 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 052d7ecd43d..08cde6c67b0 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -29,7 +29,7 @@ func ResourceListenerRule() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceListenerRuleCreate, ReadWithoutTimeout: resourceListenerRuleRead, - // UpdateWithoutTimeout: resourceListenerRuleUpdate, + UpdateWithoutTimeout: resourceListenerRuleUpdate, DeleteWithoutTimeout: resourceListenerRuleDelete, Importer: &schema.ResourceImporter{ @@ -256,7 +256,7 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameService, name, err) } - d.SetId(aws.ToString(out.Id)) + d.SetId(aws.ToString(out.Id)) //Concatinate my ids to one if _, err := waitTargetGroupCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return create.DiagError(names.VPCLattice, create.ErrActionWaitingForCreation, ResNameTargetGroup, d.Id(), err) @@ -267,6 +267,7 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() + //split the concatinate ids out, err := FindListenerRuleByID(ctx, conn, d.Id(), d.Get("listener_identifier").(string), d.Get("service_identifier").(string)) @@ -295,43 +296,30 @@ func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta return nil } -// func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - -// conn := meta.(*conns.AWSClient).VpcLatticeClient() - -// update := false - -// in := &vpclattice.BatchUpdateRuleInput{ -// Id: aws.String(d.Id()), -// } - -// if d.HasChanges("an_argument") { -// in.AnArgument = aws.String(d.Get("an_argument").(string)) -// update = true -// } +func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() -// if !update { -// // TIP: If update doesn't do anything at all, which is rare, you can -// // return nil. Otherwise, return a read call, as below. -// return nil -// } + if d.HasChangesExcept("tags", "tags_all") { + in := &vpclattice.BatchUpdateRuleInput{ + // Rules: []aws.String(d.Id()), + ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), + ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), + } -// // TIP: -- 3. Call the AWS modify/update function -// log.Printf("[DEBUG] Updating VpcLattice ListenerRule (%s): %#v", d.Id(), in) -// out, err := conn.UpdateListenerRule(ctx, in) -// if err != nil { -// return create.DiagError(names.VpcLattice, create.ErrActionUpdating, ResNameListenerRule, d.Id(), err) -// } + // out, err := + conn.BatchUpdateRule(ctx, in) -// // TIP: -- 4. Use a waiter to wait for update to complete -// if _, err := waitListenerRuleUpdated(ctx, conn, aws.ToString(out.OperationId), d.Timeout(schema.TimeoutUpdate)); err != nil { -// return create.DiagError(names.VpcLattice, create.ErrActionWaitingForUpdate, ResNameListenerRule, d.Id(), err) -// } + // if err != nil { + // return create.DiagError(names.VPCLattice, create.ErrActionUpdating, ResNameTargetGroup, d.Id(), err) + // } -// // TIP: -- 5. Call the Read function in the Update return -// return resourceListenerRuleRead(ctx, d, meta) -// } + // if _, err := waitTargetGroupUpdated(ctx, conn, aws.ToString(out.Id), d.Timeout(schema.TimeoutUpdate)); err != nil { + // return create.DiagError(names.VPCLattice, create.ErrActionWaitingForUpdate, ResNameTargetGroup, d.Id(), err) + // } + } + return resourceTargetGroupRead(ctx, d, meta) +} func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() From c6213b27ad54333cdb5e2ba0e162b64c584b215e Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Mon, 17 Apr 2023 22:12:39 +0100 Subject: [PATCH 07/15] resources created but i need to fix the path --- internal/service/vpclattice/listener_rule.go | 144 +++--------------- .../service/vpclattice/listener_rule_test.go | 77 +++++++--- 2 files changed, 78 insertions(+), 143 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 08cde6c67b0..9421582f9b7 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -166,11 +166,13 @@ func ResourceListenerRule() *schema.Resource { "case_sensitive": { Type: schema.TypeBool, Optional: true, + Computed: true, }, "match": { Type: schema.TypeList, Optional: true, MaxItems: 1, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "exact": { @@ -242,6 +244,11 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), Tags: GetTagsIn(ctx), } + + if v, ok := d.GetOk("priority"); ok { + in.Priority = aws.Int32(int32(v.(int))) + } + if v, ok := d.GetOk("action"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { in.Action = expandRuleAction(v.([]interface{})[0].(map[string]interface{})) } @@ -258,11 +265,7 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met d.SetId(aws.ToString(out.Id)) //Concatinate my ids to one - if _, err := waitTargetGroupCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return create.DiagError(names.VPCLattice, create.ErrActionWaitingForCreation, ResNameTargetGroup, d.Id(), err) - } - - return resourceTargetGroupRead(ctx, d, meta) + return resourceListenerRuleRead(ctx, d, meta) } func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -282,6 +285,7 @@ func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta } d.Set("arn", out.Arn) + d.Set("priority", out.Priority) if err := d.Set("action", []interface{}{flattenRuleAction(out.Action)}); err != nil { return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) @@ -323,7 +327,7 @@ func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, met func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() - log.Printf("[INFO] Deleting VpcLattice ListeningRule: %s", d.Id()) + log.Printf("[INFO] Deleting VpcLattice Listening Rule: %s", d.Id()) _, err := conn.DeleteRule(ctx, &vpclattice.DeleteRuleInput{ RuleIdentifier: aws.String(d.Id()), ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), @@ -336,90 +340,12 @@ func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, met return nil } - return create.DiagError(names.VPCLattice, create.ErrActionDeleting, ResNameTargetGroup, d.Id(), err) - } - - if _, err := waitTargetGroupDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return create.DiagError(names.VPCLattice, create.ErrActionWaitingForDeletion, ResNameTargetGroup, d.Id(), err) + return create.DiagError(names.VPCLattice, create.ErrActionDeleting, ResNameListenerRule, d.Id(), err) } return nil } -// const ( -// statusChangePending = "Pending" -// statusDeleting = "Deleting" -// statusNormal = "Normal" -// statusUpdated = "Updated" -// ) - -// func waitListenerRuleCreated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.ListenerRule, error) { -// stateConf := &resource.StateChangeConf{ -// Pending: []string{}, -// Target: []string{statusNormal}, -// Refresh: statusListenerRule(ctx, conn, id), -// Timeout: timeout, -// NotFoundChecks: 20, -// ContinuousTargetOccurence: 2, -// } - -// outputRaw, err := stateConf.WaitForStateContext(ctx) -// if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { -// return out, err -// } - -// return nil, err -// } - -// func waitListenerRuleUpdated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetRuleOutput, error) { -// stateConf := &resource.StateChangeConf{ -// Pending: []string{statusChangePending}, -// Target: []string{statusUpdated}, -// Refresh: statusListenerRule(ctx, conn, id), -// Timeout: timeout, -// NotFoundChecks: 20, -// ContinuousTargetOccurence: 2, -// } - -// outputRaw, err := stateConf.WaitForStateContext(ctx) -// if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { -// return out, err -// } - -// return nil, err -// } - -// func waitListenerRuleDeleted(ctx context.Context, conn *vpclattice.Client, id, listenerIdentifier, serviceIdentifier string, timeout time.Duration) (*vpclattice.GetRuleOutput, error) { -// stateConf := &resource.StateChangeConf{ -// Pending: []string{statusDeleting, statusNormal}, -// Target: []string{}, -// Refresh: statusListenerRule(ctx, conn, id, listenerIdentifier, serviceIdentifier), -// Timeout: timeout, -// } - -// outputRaw, err := stateConf.WaitForStateContext(ctx) -// if out, ok := outputRaw.(*vpclattice.ListenerRule); ok { -// return out, err -// } - -// return nil, err -// } - -// func statusListenerRule(ctx context.Context, conn *vpclattice.Client, id, listenerIdentifier, serviceIdentifier string) resource.StateRefreshFunc { -// return func() (interface{}, string, error) { -// out, err := FindListenerRuleByID(ctx, conn, id, listenerIdentifier, serviceIdentifier) -// if tfresource.NotFound(err) { -// return nil, "", nil -// } - -// if err != nil { -// return nil, "", err -// } - -// return out, aws.ToString(out.), nil -// } -// } - func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id, listenerIdentifier, serviceIdentifier string) (*vpclattice.GetRuleOutput, error) { in := &vpclattice.GetRuleInput{ RuleIdentifier: aws.String(id), @@ -456,7 +382,7 @@ func flattenRuleAction(apiObject types.RuleAction) map[string]interface{} { tfMap["fixed_response"] = flattenRuleActionMemberFixedResponse(v) } if v, ok := apiObject.(*types.RuleActionMemberForward); ok { - tfMap["forward"] = flattenForwardAction(v) + tfMap["forward"] = []interface{}{flattenForwardAction(v)} } return tfMap @@ -484,7 +410,7 @@ func flattenForwardAction(apiObject *types.RuleActionMemberForward) map[string]i tfMap := map[string]interface{}{} if v := apiObject.Value.TargetGroups; v != nil { - tfMap["forward"] = flattenWeightedTargetGroups(v) + tfMap["target_groups"] = flattenWeightedTargetGroups(v) } return tfMap @@ -531,7 +457,7 @@ func flattenRuleMatch(apiObject types.RuleMatch) map[string]interface{} { tfMap := make(map[string]interface{}) if v, ok := apiObject.(*types.RuleMatchMemberHttpMatch); ok { - tfMap["http_match"] = flattenHttpMatch(&v.Value) + tfMap["http_match"] = []interface{}{flattenHttpMatch(&v.Value)} } return tfMap @@ -549,11 +475,11 @@ func flattenHttpMatch(apiObject *types.HttpMatch) map[string]interface{} { } if v := apiObject.HeaderMatches; v != nil { - tfMap["headers_matches"] = []interface{}{flattenHeaderMatches(v)} + tfMap["headers_matches"] = flattenHeaderMatches(v) } if v := apiObject.PathMatch; v != nil { - tfMap["path_match"] = []interface{}{flattenPathMatch(v)} + tfMap["path_match"] = flattenPathMatch(v) } return tfMap @@ -591,11 +517,9 @@ func flattenHeaderMatch(apiObject *types.HeaderMatch) map[string]interface{} { if v := apiObject.Match; v != nil { if exact, ok := v.(*types.HeaderMatchTypeMemberExact); ok { tfMap["exact"] = flattenHeaderMatchTypeMemberExact(exact) - } - if prefix, ok := v.(*types.HeaderMatchTypeMemberPrefix); ok { + } else if prefix, ok := v.(*types.HeaderMatchTypeMemberPrefix); ok { tfMap["prefix"] = flattenHeaderMatchTypeMemberPrefix(prefix) - } - if contains, ok := v.(*types.HeaderMatchTypeMemberContains); ok { + } else if contains, ok := v.(*types.HeaderMatchTypeMemberContains); ok { tfMap["contains"] = flattenHeaderMatchTypeMemberContains(contains) } } @@ -603,28 +527,6 @@ func flattenHeaderMatch(apiObject *types.HeaderMatch) map[string]interface{} { return tfMap } -// func flattenHeaderMatchType(apiObject types.HeaderMatchType) map[string]interface{} { -// if apiObject == nil { -// return nil -// } - -// tfMap := make(map[string]interface{}) - -// if v, ok := apiObject.(*types.HeaderMatchTypeMemberExact); ok { -// tfMap["exact"] = []interface{}{flattenHeaderMatchTypeMemberExact(v)} -// } - -// if v, ok := apiObject.(*types.HeaderMatchTypeMemberPrefix); ok { -// tfMap["prefix"] = []interface{}{flattenHeaderMatchTypeMemberPrefix(v)} -// } - -// if v, ok := apiObject.(*types.HeaderMatchTypeMemberContains); ok { -// tfMap["contains"] = []interface{}{flattenHeaderMatchTypeMemberContains(v)} -// } - -// return tfMap -// } - func flattenHeaderMatchTypeMemberContains(apiObject *types.HeaderMatchTypeMemberContains) map[string]interface{} { if apiObject == nil { return nil @@ -675,8 +577,7 @@ func flattenPathMatch(apiObject *types.PathMatch) map[string]interface{} { if v := apiObject.Match; v != nil { if exact, ok := v.(*types.PathMatchTypeMemberExact); ok { tfMap["exact"] = flattenPathMatchTypeMemberExact(exact) - } - if prefix, ok := v.(*types.PathMatchTypeMemberPrefix); ok { + } else if prefix, ok := v.(*types.PathMatchTypeMemberPrefix); ok { tfMap["prefix"] = flattenPathMatchTypeMemberPrefix(prefix) } } @@ -711,10 +612,9 @@ func flattenPathMatchTypeMemberPrefix(apiObject *types.PathMatchTypeMemberPrefix func expandRuleAction(tfMap map[string]interface{}) types.RuleAction { var apiObject types.RuleAction - if v, ok := tfMap["fixed_response_action"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + if v, ok := tfMap["fixed_response"].([]interface{}); ok && len(v) > 0 && v[0] != nil { apiObject = expandFixedResponseAction(v[0].(map[string]interface{})) - } - if v, ok := tfMap["forward_action"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + } else if v, ok := tfMap["forward"].([]interface{}); ok && len(v) > 0 && v[0] != nil { apiObject = expandForwardAction(v[0].(map[string]interface{})) } @@ -780,7 +680,7 @@ func expandWeightedTargetGroup(tfMap map[string]interface{}) types.WeightedTarge func expandRuleMatch(tfMap map[string]interface{}) types.RuleMatch { apiObject := &types.RuleMatchMemberHttpMatch{} - if v, ok := tfMap["match"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + if v, ok := tfMap["http_match"].([]interface{}); ok && len(v) > 0 && v[0] != nil { apiObject.Value = expandHttpMatch(v[0].(map[string]interface{})) } diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go index 5bf05d68c0f..ed017afc1f4 100644 --- a/internal/service/vpclattice/listener_rule_test.go +++ b/internal/service/vpclattice/listener_rule_test.go @@ -22,7 +22,7 @@ import ( func TestAccVPCLatticeListenerRule_basic(t *testing.T) { ctx := acctest.Context(t) - var targetGroup vpclattice.GetTargetGroupOutput + var listenerRule vpclattice.GetRuleOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_vpclattice_listener_rule.test" @@ -34,13 +34,13 @@ func TestAccVPCLatticeListenerRule_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckListenerRuleDestroy(ctx), + CheckDestroy: testAccCheckListeningRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupConfig_basic(rName), + Config: testAccListeningRule_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("targetgroup/.+$")), + testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile(`service/.+/listener/.+/rule/.+`)), ), }, { @@ -53,21 +53,36 @@ func TestAccVPCLatticeListenerRule_basic(t *testing.T) { } func testAccListeningRule_basic(rName string) string { - return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` -resource "aws_vpclattice_listener_rule" "test" { - listener_identifier = "arn:aws:vpc-lattice:eu-west-1:926562225508:service/svc-05cad7dcf6ee78d45/listener/listener-061ead87f8dcf282c" - service_identifier = "arn:aws:vpc-lattice:eu-west-1:926562225508:service/svc-05cad7dcf6ee78d45" - action { - target_group_arn = "arn:aws:vpc-lattice:eu-west-1:926562225508:targetgroup/tg-00153386728e69d10" - } - match { - path_match = "/" + return fmt.Sprintf(` + resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0238d4e9479096392" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 93 + action { + forward { + target_groups{ + target_group_identifier = "tg-00153386728e69d10" + weight = 1 + } + } + + } + match { + http_match { + path_match { + case_sensitive = false + match { + exact = "/example-path" + } + } + } + } } -} -`, rName)) +`, rName) } -func testAccCheckListeningRuleExists(ctx context.Context, name string, targetGroup *vpclattice.GetRuleOutput) resource.TestCheckFunc { +func testAccCheckListenerRuleExists(ctx context.Context, name string, listenerRule *vpclattice.GetRuleOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -78,22 +93,32 @@ func testAccCheckListeningRuleExists(ctx context.Context, name string, targetGro return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not set")) } + listenerRuleResource, ok := s.RootModule().Resources["aws_vpclattice_listener_rule.test"] + if !ok { + return fmt.Errorf("Not found: %s", "aws_vpclattice_listener_rule.test") + } + + listenerIdentifier := listenerRuleResource.Primary.Attributes["listener_identifier"] + serviceIdentifier := listenerRuleResource.Primary.Attributes["service_identifier"] + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() resp, err := conn.GetRule(ctx, &vpclattice.GetRuleInput{ - ListenerIdentifier: aws.String(rs.Primary.ID), + RuleIdentifier: aws.String(rs.Primary.ID), + ListenerIdentifier: aws.String(listenerIdentifier), + ServiceIdentifier: aws.String(serviceIdentifier), }) if err != nil { return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, rs.Primary.ID, err) } - *targetGroup = *resp + *listenerRule = *resp return nil } } -func testAccCheckListenerRuleDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckListeningRuleDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() @@ -102,8 +127,18 @@ func testAccCheckListenerRuleDestroy(ctx context.Context) resource.TestCheckFunc continue } + listenerRuleResource, ok := s.RootModule().Resources["aws_vpclattice_listener_rule.test"] + if !ok { + return fmt.Errorf("Not found: %s", "aws_vpclattice_listener_rule.test") + } + + listenerIdentifier := listenerRuleResource.Primary.Attributes["listener_identifier"] + serviceIdentifier := listenerRuleResource.Primary.Attributes["service_identifier"] + _, err := conn.GetRule(ctx, &vpclattice.GetRuleInput{ - ListenerIdentifier: aws.String(rs.Primary.ID), + RuleIdentifier: aws.String(rs.Primary.ID), + ListenerIdentifier: aws.String(listenerIdentifier), + ServiceIdentifier: aws.String(serviceIdentifier), }) if err != nil { var nfe *types.ResourceNotFoundException From 18f07cc4b0f45c8dfc5a6b165c90c4e86d143018 Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Wed, 19 Apr 2023 09:09:21 +0100 Subject: [PATCH 08/15] fixed type error --- internal/service/vpclattice/listener_rule.go | 293 +++++++++++++----- .../service/vpclattice/listener_rule_test.go | 53 ++-- 2 files changed, 240 insertions(+), 106 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 9421582f9b7..caf13a0aeda 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -2,8 +2,11 @@ package vpclattice import ( "context" + "encoding/json" "errors" + "fmt" "log" + "strings" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -33,7 +36,20 @@ func ResourceListenerRule() *schema.Resource { DeleteWithoutTimeout: resourceListenerRuleDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), "/") + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected SERVICE-ID/LISTENER-ID/RULE-ID", d.Id()) + } + serviceIdentifier := idParts[0] + listenerIdentifier := idParts[1] + ruleId := idParts[2] + d.Set("service_identifier", serviceIdentifier) + d.Set("listener_identifier", listenerIdentifier) + d.Set("rule_id", ruleId) + + return []*schema.ResourceData{d}, nil + }, }, Timeouts: &schema.ResourceTimeout{ @@ -50,7 +66,7 @@ func ResourceListenerRule() *schema.Resource { "action": { Type: schema.TypeList, MaxItems: 1, - Optional: true, + Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "fixed_response": { @@ -66,7 +82,6 @@ func ResourceListenerRule() *schema.Resource { }, }, }, - DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, }, "forward": { Type: schema.TypeList, @@ -88,7 +103,7 @@ func ResourceListenerRule() *schema.Resource { "weight": { Type: schema.TypeInt, ValidateFunc: validation.IntBetween(0, 999), - Default: 1, + Default: 100, Optional: true, }, }, @@ -96,33 +111,34 @@ func ResourceListenerRule() *schema.Resource { }, }, }, - DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, }, }, }, }, "match": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, + Type: schema.TypeList, + Required: true, + MaxItems: 1, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "http_match": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "method": { Type: schema.TypeString, - Computed: true, Optional: true, }, - "headers_matches": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - MaxItems: 5, + "header_matches": { + Type: schema.TypeList, + Optional: true, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, + MinItems: 1, + MaxItems: 5, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "case_sensitive": { @@ -130,9 +146,10 @@ func ResourceListenerRule() *schema.Resource { Optional: true, }, "match": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Required: true, + MaxItems: 1, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "contains": { @@ -152,7 +169,7 @@ func ResourceListenerRule() *schema.Resource { }, "name": { Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -166,13 +183,11 @@ func ResourceListenerRule() *schema.Resource { "case_sensitive": { Type: schema.TypeBool, Optional: true, - Computed: true, }, "match": { Type: schema.TypeList, - Optional: true, + Required: true, MaxItems: 1, - Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "exact": { @@ -208,15 +223,37 @@ func ResourceListenerRule() *schema.Resource { ForceNew: false, }, - "listener_identifier": { + "rule_id": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Computed: true, }, + + "listener_arn": { + Type: schema.TypeString, + Computed: true, + Optional: true, + AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, + }, + + "listener_identifier": { + Type: schema.TypeString, + Computed: true, + Optional: true, + AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, + }, + + "service_arn": { + Type: schema.TypeString, + Computed: true, + Optional: true, + AtLeastOneOf: []string{"service_arn", "service_identifier"}, + }, + "service_identifier": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Computed: true, + Optional: true, + AtLeastOneOf: []string{"service_arn", "service_identifier"}, }, names.AttrTags: tftags.TagsSchema(), @@ -238,41 +275,73 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met name := d.Get("name").(string) in := &vpclattice.CreateRuleInput{ - ClientToken: aws.String(id.UniqueId()), - Name: aws.String(name), - ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), - ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), - Tags: GetTagsIn(ctx), + ClientToken: aws.String(id.UniqueId()), + Name: aws.String(name), + Action: expandRuleAction(d.Get("action").([]interface{})[0].(map[string]interface{})), + Match: expandRuleMatch(d.Get("match").([]interface{})[0].(map[string]interface{})), + Tags: GetTagsIn(ctx), } if v, ok := d.GetOk("priority"); ok { in.Priority = aws.Int32(int32(v.(int))) } - if v, ok := d.GetOk("action"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - in.Action = expandRuleAction(v.([]interface{})[0].(map[string]interface{})) + if v, ok := d.GetOk("service_identifier"); ok { + in.ServiceIdentifier = aws.String(v.(string)) } - if v, ok := d.GetOk("match"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - in.Match = expandRuleMatch(v.([]interface{})[0].(map[string]interface{})) + if v, ok := d.GetOk("service_arn"); ok { + in.ServiceIdentifier = aws.String(v.(string)) } - out, err := conn.CreateRule(ctx, in) + if in.ServiceIdentifier == nil { + return diag.FromErr(fmt.Errorf("must specify either service_arn or service_identifier")) + } + + if v, ok := d.GetOk("listener_identifier"); ok { + in.ListenerIdentifier = aws.String(v.(string)) + } + if v, ok := d.GetOk("listener_arn"); ok { + in.ListenerIdentifier = aws.String(v.(string)) + } + + if in.ListenerIdentifier == nil { + return diag.FromErr(fmt.Errorf("must specify either listener_arn or listener_identifier")) + } + + out, err := conn.CreateRule(ctx, in) if err != nil { - return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameService, name, err) + return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameListenerRule, name, err) + } + + if out == nil || out.Arn == nil { + return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameListenerRule, d.Get("name").(string), errors.New("empty output")) + } + + d.Set("rule_id", out.Id) + d.Set("service_identifier", in.ServiceIdentifier) + d.Set("listener_identifier", in.ListenerIdentifier) + + parts := []string{ + d.Get("service_identifier").(string), + d.Get("listener_identifier").(string), + d.Get("rule_id").(string), } - d.SetId(aws.ToString(out.Id)) //Concatinate my ids to one + d.SetId(strings.Join(parts, "/")) return resourceListenerRuleRead(ctx, d, meta) } func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() - //split the concatinate ids - out, err := FindListenerRuleByID(ctx, conn, d.Id(), d.Get("listener_identifier").(string), d.Get("service_identifier").(string)) + serviceId := d.Get("service_identifier").(string) + listenerId := d.Get("listener_identifier").(string) + ruleId := d.Get("rule_id").(string) + + out, err := FindListenerRuleByID(ctx, conn, serviceId, listenerId, ruleId) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] VpcLattice Listener Rule (%s) not found, removing from state", d.Id()) @@ -283,9 +352,14 @@ func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta if err != nil { return create.DiagError(names.VPCLattice, create.ErrActionReading, ResNameListenerRule, d.Id(), err) } + fmt.Println("Pupulating") d.Set("arn", out.Arn) d.Set("priority", out.Priority) + d.Set("name", out.Name) + d.Set("listener_identifier", listenerId) + d.Set("service_identifier", serviceId) + d.Set("rule_id", out.Id) if err := d.Set("action", []interface{}{flattenRuleAction(out.Action)}); err != nil { return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) @@ -295,43 +369,57 @@ func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameListenerRule, d.Id(), err) } - d.Set("name", out.Name) - return nil } func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() + serviceId := d.Get("service_identifier").(string) + listenerId := d.Get("listener_identifier").(string) + ruleId := d.Get("rule_id").(string) + if d.HasChangesExcept("tags", "tags_all") { - in := &vpclattice.BatchUpdateRuleInput{ - // Rules: []aws.String(d.Id()), - ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), - ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), + + in := &vpclattice.UpdateRuleInput{ + RuleIdentifier: aws.String(ruleId), + ListenerIdentifier: aws.String(listenerId), + ServiceIdentifier: aws.String(serviceId), } - // out, err := - conn.BatchUpdateRule(ctx, in) + if d.HasChange("action") { + if v, ok := d.GetOk("action"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.Action = expandRuleAction(v.([]interface{})[0].(map[string]interface{})) + } + } - // if err != nil { - // return create.DiagError(names.VPCLattice, create.ErrActionUpdating, ResNameTargetGroup, d.Id(), err) - // } + if d.HasChange("match") { + if v, ok := d.GetOk("match"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.Match = expandRuleMatch(v.([]interface{})[0].(map[string]interface{})) + } + } + _, err := conn.UpdateRule(ctx, in) + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionUpdating, ResNameListenerRule, d.Id(), err) + } - // if _, err := waitTargetGroupUpdated(ctx, conn, aws.ToString(out.Id), d.Timeout(schema.TimeoutUpdate)); err != nil { - // return create.DiagError(names.VPCLattice, create.ErrActionWaitingForUpdate, ResNameTargetGroup, d.Id(), err) - // } } - return resourceTargetGroupRead(ctx, d, meta) + return resourceListenerRuleRead(ctx, d, meta) } + func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() + serviceId := d.Get("service_identifier").(string) + listenerId := d.Get("listener_identifier").(string) + ruleId := d.Get("rule_id").(string) + log.Printf("[INFO] Deleting VpcLattice Listening Rule: %s", d.Id()) _, err := conn.DeleteRule(ctx, &vpclattice.DeleteRuleInput{ - RuleIdentifier: aws.String(d.Id()), - ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), - ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), + ListenerIdentifier: aws.String(listenerId), + RuleIdentifier: aws.String(ruleId), + ServiceIdentifier: aws.String(serviceId), }) if err != nil { @@ -346,10 +434,11 @@ func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, met return nil } -func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, id, listenerIdentifier, serviceIdentifier string) (*vpclattice.GetRuleOutput, error) { +func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, serviceIdentifier string, listenerIdentifier string, ruleId string) (*vpclattice.GetRuleOutput, error) { + in := &vpclattice.GetRuleInput{ - RuleIdentifier: aws.String(id), ListenerIdentifier: aws.String(listenerIdentifier), + RuleIdentifier: aws.String(ruleId), ServiceIdentifier: aws.String(serviceIdentifier), } out, err := conn.GetRule(ctx, in) @@ -475,7 +564,7 @@ func flattenHttpMatch(apiObject *types.HttpMatch) map[string]interface{} { } if v := apiObject.HeaderMatches; v != nil { - tfMap["headers_matches"] = flattenHeaderMatches(v) + tfMap["header_matches"] = flattenHeaderMatches(v) } if v := apiObject.PathMatch; v != nil { @@ -515,15 +604,26 @@ func flattenHeaderMatch(apiObject *types.HeaderMatch) map[string]interface{} { } if v := apiObject.Match; v != nil { - if exact, ok := v.(*types.HeaderMatchTypeMemberExact); ok { - tfMap["exact"] = flattenHeaderMatchTypeMemberExact(exact) - } else if prefix, ok := v.(*types.HeaderMatchTypeMemberPrefix); ok { - tfMap["prefix"] = flattenHeaderMatchTypeMemberPrefix(prefix) - } else if contains, ok := v.(*types.HeaderMatchTypeMemberContains); ok { - tfMap["contains"] = flattenHeaderMatchTypeMemberContains(contains) - } + tfMap["match"] = []interface{}{flattenHeaderMatchType(v)} + } + fmt.Println(tfMap) + return tfMap +} +func flattenHeaderMatchType(apiObject types.HeaderMatchType) map[string]interface{} { + if apiObject == nil { + return nil } + tfMap := make(map[string]interface{}) + + if v, ok := apiObject.(*types.HeaderMatchTypeMemberContains); ok { + return flattenHeaderMatchTypeMemberContains(v) + } else if v, ok := apiObject.(*types.HeaderMatchTypeMemberExact); ok { + return flattenHeaderMatchTypeMemberExact(v) + } else if v, ok := apiObject.(*types.HeaderMatchTypeMemberPrefix); ok { + return flattenHeaderMatchTypeMemberPrefix(v) + } + fmt.Println(tfMap) return tfMap } @@ -536,6 +636,7 @@ func flattenHeaderMatchTypeMemberContains(apiObject *types.HeaderMatchTypeMember "contains": apiObject.Value, } + fmt.Println(tfMap) return tfMap } @@ -563,7 +664,7 @@ func flattenHeaderMatchTypeMemberPrefix(apiObject *types.HeaderMatchTypeMemberPr return tfMap } -func flattenPathMatch(apiObject *types.PathMatch) map[string]interface{} { +func flattenPathMatch(apiObject *types.PathMatch) []interface{} { if apiObject == nil { return nil } @@ -575,13 +676,26 @@ func flattenPathMatch(apiObject *types.PathMatch) map[string]interface{} { } if v := apiObject.Match; v != nil { - if exact, ok := v.(*types.PathMatchTypeMemberExact); ok { - tfMap["exact"] = flattenPathMatchTypeMemberExact(exact) - } else if prefix, ok := v.(*types.PathMatchTypeMemberPrefix); ok { - tfMap["prefix"] = flattenPathMatchTypeMemberPrefix(prefix) - } + tfMap["match"] = []interface{}{flattenPathMatchType(v)} + } + + return []interface{}{tfMap} +} + +func flattenPathMatchType(apiObject types.PathMatchType) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v, ok := apiObject.(*types.PathMatchTypeMemberExact); ok { + return flattenPathMatchTypeMemberExact(v) + } else if v, ok := apiObject.(*types.PathMatchTypeMemberPrefix); ok { + return flattenPathMatchTypeMemberPrefix(v) } + fmt.Println(tfMap) return tfMap } @@ -678,11 +792,18 @@ func expandWeightedTargetGroup(tfMap map[string]interface{}) types.WeightedTarge } func expandRuleMatch(tfMap map[string]interface{}) types.RuleMatch { + fmt.Println("Running Expand Rule Match") apiObject := &types.RuleMatchMemberHttpMatch{} if v, ok := tfMap["http_match"].([]interface{}); ok && len(v) > 0 && v[0] != nil { apiObject.Value = expandHttpMatch(v[0].(map[string]interface{})) + } + j, err := json.Marshal(apiObject) + if err != nil { + log.Fatal(err) + } + fmt.Println("Returning from expandRuleMatch:", string(j)) return apiObject } @@ -698,10 +819,14 @@ func expandHttpMatch(tfMap map[string]interface{}) types.HttpMatch { apiObject.Method = aws.String(v) } - if v, ok := tfMap["matcher"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + if v, ok := tfMap["path_match"].([]interface{}); ok && len(v) > 0 && v != nil { apiObject.PathMatch = expandPathMatch(v[0].(map[string]interface{})) } - + j, err := json.Marshal(apiObject) + if err != nil { + log.Fatal(err) + } + fmt.Println("Returning from expandHttpMatch:", string(j)) return apiObject } @@ -720,6 +845,11 @@ func expandHeaderMatches(tfList []interface{}) []types.HeaderMatch { } apiObject := expandHeaderMatch(tfMap) + j, err := json.Marshal(expandHeaderMatch(tfMap)) + if err != nil { + log.Fatal(err) + } + fmt.Println("Returning from defaultAction:", string(j)) apiObjects = append(apiObjects, apiObject) } @@ -790,11 +920,9 @@ func expandPathMatch(tfMap map[string]interface{}) *types.PathMatch { if v, ok := tfMap["match"].([]interface{}); ok && len(v) > 0 { matchObj := v[0].(map[string]interface{}) - if matchV, ok := matchObj["exact"].(string); ok && matchV != "" { apiObject.Match = expandPathMatchTypeMemberExact(matchObj) } - if matchV, ok := matchObj["prefix"].(string); ok && matchV != "" { apiObject.Match = expandPathMatchTypeMemberPrefix(matchObj) } @@ -809,6 +937,7 @@ func expandPathMatchTypeMemberExact(tfMap map[string]interface{}) types.PathMatc if v, ok := tfMap["exact"].(string); ok && v != "" { apiObject.Value = v } + return apiObject } diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go index ed017afc1f4..b7b807dc2d8 100644 --- a/internal/service/vpclattice/listener_rule_test.go +++ b/internal/service/vpclattice/listener_rule_test.go @@ -40,7 +40,7 @@ func TestAccVPCLatticeListenerRule_basic(t *testing.T) { Config: testAccListeningRule_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile(`service/.+/listener/.+/rule/.+`)), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile(`service/svc-.*/listener/listener-.*/rule/rule.+`)), ), }, { @@ -56,9 +56,29 @@ func testAccListeningRule_basic(rName string) string { return fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { name = %q - listener_identifier = "listener-0238d4e9479096392" + listener_identifier = "listener-06284aa0e76529ac7" service_identifier = "svc-05cad7dcf6ee78d45" - priority = 93 + priority = 13 + match { + http_match { + + header_matches { + name = "example-header" + case_sensitive = true + + match { + exact = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + } + } action { forward { target_groups{ @@ -68,21 +88,11 @@ func testAccListeningRule_basic(rName string) string { } } - match { - http_match { - path_match { - case_sensitive = false - match { - exact = "/example-path" - } - } - } - } } `, rName) } -func testAccCheckListenerRuleExists(ctx context.Context, name string, listenerRule *vpclattice.GetRuleOutput) resource.TestCheckFunc { +func testAccCheckListenerRuleExists(ctx context.Context, name string, rule *vpclattice.GetRuleOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -93,17 +103,12 @@ func testAccCheckListenerRuleExists(ctx context.Context, name string, listenerRu return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, name, errors.New("not set")) } - listenerRuleResource, ok := s.RootModule().Resources["aws_vpclattice_listener_rule.test"] - if !ok { - return fmt.Errorf("Not found: %s", "aws_vpclattice_listener_rule.test") - } - - listenerIdentifier := listenerRuleResource.Primary.Attributes["listener_identifier"] - serviceIdentifier := listenerRuleResource.Primary.Attributes["service_identifier"] + serviceIdentifier := rs.Primary.Attributes["service_identifier"] + listenerIdentifier := rs.Primary.Attributes["listener_identifier"] conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() resp, err := conn.GetRule(ctx, &vpclattice.GetRuleInput{ - RuleIdentifier: aws.String(rs.Primary.ID), + RuleIdentifier: aws.String(rs.Primary.Attributes["arn"]), ListenerIdentifier: aws.String(listenerIdentifier), ServiceIdentifier: aws.String(serviceIdentifier), }) @@ -112,7 +117,7 @@ func testAccCheckListenerRuleExists(ctx context.Context, name string, listenerRu return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListenerRule, rs.Primary.ID, err) } - *listenerRule = *resp + *rule = *resp return nil } @@ -136,7 +141,7 @@ func testAccCheckListeningRuleDestroy(ctx context.Context) resource.TestCheckFun serviceIdentifier := listenerRuleResource.Primary.Attributes["service_identifier"] _, err := conn.GetRule(ctx, &vpclattice.GetRuleInput{ - RuleIdentifier: aws.String(rs.Primary.ID), + RuleIdentifier: aws.String(rs.Primary.Attributes["arn"]), ListenerIdentifier: aws.String(listenerIdentifier), ServiceIdentifier: aws.String(serviceIdentifier), }) From e1777790a868114064cad07c3c13a3bc2b58b2a4 Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Wed, 19 Apr 2023 11:24:29 +0100 Subject: [PATCH 09/15] finalised tests --- internal/service/vpclattice/listener_rule.go | 43 +-- .../service/vpclattice/listener_rule_test.go | 252 +++++++++++++++++- 2 files changed, 256 insertions(+), 39 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index caf13a0aeda..580a91f4bbb 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -2,7 +2,6 @@ package vpclattice import ( "context" - "encoding/json" "errors" "fmt" "log" @@ -76,9 +75,8 @@ func ResourceListenerRule() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "status_code": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(100, 599), + Type: schema.TypeInt, + Required: true, }, }, }, @@ -214,13 +212,12 @@ func ResourceListenerRule() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validation.StringLenBetween(3, 128), + ValidateFunc: validation.StringLenBetween(3, 63), }, "priority": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: false, + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 100), }, "rule_id": { @@ -352,7 +349,6 @@ func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta if err != nil { return create.DiagError(names.VPCLattice, create.ErrActionReading, ResNameListenerRule, d.Id(), err) } - fmt.Println("Pupulating") d.Set("arn", out.Arn) d.Set("priority", out.Priority) @@ -468,7 +464,7 @@ func flattenRuleAction(apiObject types.RuleAction) map[string]interface{} { tfMap := make(map[string]interface{}) if v, ok := apiObject.(*types.RuleActionMemberFixedResponse); ok { - tfMap["fixed_response"] = flattenRuleActionMemberFixedResponse(v) + tfMap["fixed_response"] = []interface{}{flattenRuleActionMemberFixedResponse(v)} } if v, ok := apiObject.(*types.RuleActionMemberForward); ok { tfMap["forward"] = []interface{}{flattenForwardAction(v)} @@ -606,7 +602,7 @@ func flattenHeaderMatch(apiObject *types.HeaderMatch) map[string]interface{} { if v := apiObject.Match; v != nil { tfMap["match"] = []interface{}{flattenHeaderMatchType(v)} } - fmt.Println(tfMap) + return tfMap } func flattenHeaderMatchType(apiObject types.HeaderMatchType) map[string]interface{} { @@ -623,7 +619,7 @@ func flattenHeaderMatchType(apiObject types.HeaderMatchType) map[string]interfac } else if v, ok := apiObject.(*types.HeaderMatchTypeMemberPrefix); ok { return flattenHeaderMatchTypeMemberPrefix(v) } - fmt.Println(tfMap) + return tfMap } @@ -636,7 +632,6 @@ func flattenHeaderMatchTypeMemberContains(apiObject *types.HeaderMatchTypeMember "contains": apiObject.Value, } - fmt.Println(tfMap) return tfMap } @@ -695,7 +690,6 @@ func flattenPathMatchType(apiObject types.PathMatchType) map[string]interface{} return flattenPathMatchTypeMemberPrefix(v) } - fmt.Println(tfMap) return tfMap } @@ -738,7 +732,7 @@ func expandRuleAction(tfMap map[string]interface{}) types.RuleAction { func expandFixedResponseAction(tfMap map[string]interface{}) *types.RuleActionMemberFixedResponse { apiObject := &types.RuleActionMemberFixedResponse{} - if v, ok := tfMap["status"].(int); ok && v != 0 { + if v, ok := tfMap["status_code"].(int); ok && v != 0 { apiObject.Value.StatusCode = aws.Int32(int32(v)) } @@ -792,18 +786,12 @@ func expandWeightedTargetGroup(tfMap map[string]interface{}) types.WeightedTarge } func expandRuleMatch(tfMap map[string]interface{}) types.RuleMatch { - fmt.Println("Running Expand Rule Match") apiObject := &types.RuleMatchMemberHttpMatch{} if v, ok := tfMap["http_match"].([]interface{}); ok && len(v) > 0 && v[0] != nil { apiObject.Value = expandHttpMatch(v[0].(map[string]interface{})) } - j, err := json.Marshal(apiObject) - if err != nil { - log.Fatal(err) - } - fmt.Println("Returning from expandRuleMatch:", string(j)) return apiObject } @@ -822,11 +810,7 @@ func expandHttpMatch(tfMap map[string]interface{}) types.HttpMatch { if v, ok := tfMap["path_match"].([]interface{}); ok && len(v) > 0 && v != nil { apiObject.PathMatch = expandPathMatch(v[0].(map[string]interface{})) } - j, err := json.Marshal(apiObject) - if err != nil { - log.Fatal(err) - } - fmt.Println("Returning from expandHttpMatch:", string(j)) + return apiObject } @@ -845,11 +829,6 @@ func expandHeaderMatches(tfList []interface{}) []types.HeaderMatch { } apiObject := expandHeaderMatch(tfMap) - j, err := json.Marshal(expandHeaderMatch(tfMap)) - if err != nil { - log.Fatal(err) - } - fmt.Println("Returning from defaultAction:", string(j)) apiObjects = append(apiObjects, apiObject) } diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go index b7b807dc2d8..9f7c3318856 100644 --- a/internal/service/vpclattice/listener_rule_test.go +++ b/internal/service/vpclattice/listener_rule_test.go @@ -34,12 +34,13 @@ func TestAccVPCLatticeListenerRule_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckListeningRuleDestroy(ctx), + CheckDestroy: testAccChecklistenerRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccListeningRule_basic(rName), + Config: testAcclistenerRule_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), + resource.TestCheckResourceAttr(resourceName, "priority", "20"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile(`service/svc-.*/listener/listener-.*/rule/rule.+`)), ), }, @@ -52,19 +53,120 @@ func TestAccVPCLatticeListenerRule_basic(t *testing.T) { }) } -func testAccListeningRule_basic(rName string) string { +func TestAccVPCLatticeListenerRule_fixedResponse(t *testing.T) { + ctx := acctest.Context(t) + var listenerRule vpclattice.GetRuleOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_listener_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccChecklistenerRuleDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAcclistenerRule_fixedResponse(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "priority", "10"), + resource.TestCheckResourceAttr(resourceName, "action.0.fixed_response.0.status_code", "404"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccVPCLatticeListenerRule_methodMatch(t *testing.T) { + ctx := acctest.Context(t) + var listenerRule vpclattice.GetRuleOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_listener_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccChecklistenerRuleDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAcclistenerRule_methodMatch(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "priority", "40"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccVPCLatticeListenerRule_tags(t *testing.T) { + ctx := acctest.Context(t) + var listenerRule vpclattice.GetRuleOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_listener_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccChecklistenerRuleDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccListenerRule_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccListenerRule_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAcclistenerRule_basic(rName string) string { return fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { name = %q - listener_identifier = "listener-06284aa0e76529ac7" + listener_identifier = "listener-0f84b4608eae14610" service_identifier = "svc-05cad7dcf6ee78d45" - priority = 13 + priority = 20 match { http_match { header_matches { name = "example-header" - case_sensitive = true + case_sensitive = false match { exact = "example-contains" @@ -85,6 +187,10 @@ func testAccListeningRule_basic(rName string) string { target_group_identifier = "tg-00153386728e69d10" weight = 1 } + target_groups{ + target_group_identifier = "tg-0fcd8d514d231b311" + weight = 2 + } } } @@ -92,6 +198,33 @@ func testAccListeningRule_basic(rName string) string { `, rName) } +func testAcclistenerRule_fixedResponse(rName string) string { + return fmt.Sprintf(` + + resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 10 + match { + http_match { + path_match { + case_sensitive = false + match { + exact = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } + } +`, rName) +} + func testAccCheckListenerRuleExists(ctx context.Context, name string, rule *vpclattice.GetRuleOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -123,7 +256,7 @@ func testAccCheckListenerRuleExists(ctx context.Context, name string, rule *vpcl } } -func testAccCheckListeningRuleDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccChecklistenerRuleDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() @@ -159,3 +292,108 @@ func testAccCheckListeningRuleDestroy(ctx context.Context) resource.TestCheckFun return nil } } + +func testAccListenerRule_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` + resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 30 + match { + http_match { + path_match { + case_sensitive = false + match { + prefix = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } + tags = { + %[2]q = %[3]q + } + } +`, rName, tagKey1, tagValue1) +} + +func testAccListenerRule_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` + resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 30 + match { + http_match { + path_match { + case_sensitive = false + match { + prefix = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } + } +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAcclistenerRule_methodMatch(rName string) string { + return fmt.Sprintf(` + resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 40 + match { + http_match { + + method = "POST" + + header_matches { + name = "example-header" + case_sensitive = false + + match { + contains = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + + } + } + action { + forward { + target_groups { + target_group_identifier = "tg-00153386728e69d10" + weight = 1 + } + target_groups { + target_group_identifier = "tg-0fcd8d514d231b311" + weight = 2 + } + } + } + } +`, rName) +} From 781fd42993aac8b6a4bddd080d1b3f0cf51a249f Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Wed, 19 Apr 2023 12:02:12 +0100 Subject: [PATCH 10/15] wrote the documentation --- .changelog/30784.txt | 3 + .../r/vpclattice_listener_rule.html.markdown | 117 +++++++++++++++++- 2 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 .changelog/30784.txt diff --git a/.changelog/30784.txt b/.changelog/30784.txt new file mode 100644 index 00000000000..ce99935fbb6 --- /dev/null +++ b/.changelog/30784.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_vpclattice_listener_rule +``` \ No newline at end of file diff --git a/website/docs/r/vpclattice_listener_rule.html.markdown b/website/docs/r/vpclattice_listener_rule.html.markdown index a526f12a47c..8ead89f1a91 100644 --- a/website/docs/r/vpclattice_listener_rule.html.markdown +++ b/website/docs/r/vpclattice_listener_rule.html.markdown @@ -11,30 +11,135 @@ description: |- Terraform resource for managing an AWS VPC Lattice Listener Rule. ## Example Usage +```terraform + resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = aws_vpclattice_listener.example.id + service_identifier = aws_vpclattice_service.example.id + priority = 20 + match { + http_match { + + header_matches { + name = "example-header" + case_sensitive = false + + match { + exact = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + } + } + action { + forward { + target_groups{ + target_group_identifier = aws_vpclattice_target_group.example.id + weight = 1 + } + target_groups{ + target_group_identifier = aws_vpclattice_target_group.example2.id + weight = 2 + } + } + + } + } +``` ### Basic Usage ```terraform -resource "aws_vpclattice_listener_rule" "example" { -} + resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = aws_vpclattice_listener.example.id + service_identifier = aws_vpclattice_service.example.id + priority = 10 + match { + http_match { + path_match { + case_sensitive = false + match { + exact = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } + } ``` ## Argument Reference The following arguments are required: -* `example_arg` - (Required) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `service_identifier` - (Required) The ID or Amazon Resource Identifier (ARN) of the service. +* `listener_identifier` - (Required) The ID or Amazon Resource Name (ARN) of the listener. +* `action` - (Required) The action for the default rule. +* `match` - (Required) The rule match. +* `name` - (Required) The name of the rule. The name must be unique within the listener. The valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen. +* `priority` - (Required) The priority assigned to the rule. Each rule for a specific listener must have a unique priority. The lower the priority number the higher the priority. + The following arguments are optional: -* `optional_arg` - (Optional) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `tags` - (Optional) Key-value mapping of resource tags. 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. + +action (`action`) supports the following: +* `fixed_responce` - (Optional) Describes the rule action that returns a custom HTTP response. +* `forward` - (Optional) The forward action. Traffic that matches the rule is forwarded to the specified target groups. + +fixed responce (`fixed_responce`) supports the following: +* `status_code` - (Optional) The HTTP response code. + +forward (`forward`) supports the following: +* `target_groups` - (Optional) The target groups. Traffic matching the rule is forwarded to the specified target groups. With forward actions, you can assign a weight that controls the prioritization and selection of each target group. This means that requests are distributed to individual target groups based on their weights. For example, if two target groups have the same weight, each target group receives half of the traffic. + +The default value is 1 with maximum number of 2. If only one target group is provided, there is no need to set the weight; 100% of traffic will go to that target group. + +action (`match`) supports the following: +* `http_match` - (Optional) The HTTP criteria that a rule must match. + +http match (`http_match`) supports the following: +* `header_matches` - (Optional) The header matches. Matches incoming requests with rule based on request header value before applying rule action. +* `method` - (Optional) The HTTP method type. +* `path_match` - (Optional) The path match. + +header matches (`header_matches`) supports the following: +* `case_sensitive` - (Optional) Indicates whether the match is case sensitive. Defaults to false. +* `match` - (Optional) The header match type. +* `name` - (Optional) The name of the header. + +header matches match (`match`) supports the following: +* `contains` - (Optional) Specifies a contains type match. +* `exact` - (Optional) Specifies an exact type match. +* `prefix` - (Optional) Specifies a prefix type match. Matches the value with the prefix. + +path match (`path_match`) supports the following: +* `case_sensitive` - (Optional) Indicates whether the match is case sensitive. Defaults to false. +* `match` - (Optional) The header match type. + +path match match (`match`) supports the following: +* `exact` - (Optional) Specifies an exact type match. +* `prefix` - (Optional) Specifies a prefix type match. Matches the value with the prefix. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `arn` - ARN of the Listener Rule. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. -* `example_attribute` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `arn` - ARN of the target group. +* `rule_id` - Unique identifier for the target group. +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). ## Timeouts From a7aeb1e5910fa63cfc259b5f1c1274e71dcb6bff Mon Sep 17 00:00:00 2001 From: Markos Kandylis Date: Wed, 19 Apr 2023 12:54:00 +0100 Subject: [PATCH 11/15] made checks changes --- internal/service/vpclattice/listener_rule.go | 13 +- .../service/vpclattice/listener_rule_test.go | 301 +++++++++--------- .../r/vpclattice_listener_rule.html.markdown | 135 ++++---- 3 files changed, 227 insertions(+), 222 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 580a91f4bbb..3cf6195fa39 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -376,7 +376,6 @@ func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, met ruleId := d.Get("rule_id").(string) if d.HasChangesExcept("tags", "tags_all") { - in := &vpclattice.UpdateRuleInput{ RuleIdentifier: aws.String(ruleId), ListenerIdentifier: aws.String(listenerId), @@ -398,7 +397,6 @@ func resourceListenerRuleUpdate(ctx context.Context, d *schema.ResourceData, met if err != nil { return create.DiagError(names.VPCLattice, create.ErrActionUpdating, ResNameListenerRule, d.Id(), err) } - } return resourceListenerRuleRead(ctx, d, meta) @@ -431,7 +429,6 @@ func resourceListenerRuleDelete(ctx context.Context, d *schema.ResourceData, met } func FindListenerRuleByID(ctx context.Context, conn *vpclattice.Client, serviceIdentifier string, listenerIdentifier string, ruleId string) (*vpclattice.GetRuleOutput, error) { - in := &vpclattice.GetRuleInput{ ListenerIdentifier: aws.String(listenerIdentifier), RuleIdentifier: aws.String(ruleId), @@ -516,7 +513,6 @@ func flattenWeightedTargetGroups(apiObjects []types.WeightedTargetGroup) []inter } func flattenWeightedTargetGroup(apiObject *types.WeightedTargetGroup) map[string]interface{} { - if apiObject == nil { return nil } @@ -542,13 +538,13 @@ func flattenRuleMatch(apiObject types.RuleMatch) map[string]interface{} { tfMap := make(map[string]interface{}) if v, ok := apiObject.(*types.RuleMatchMemberHttpMatch); ok { - tfMap["http_match"] = []interface{}{flattenHttpMatch(&v.Value)} + tfMap["http_match"] = []interface{}{flattenHTTPMatch(&v.Value)} } return tfMap } -func flattenHttpMatch(apiObject *types.HttpMatch) map[string]interface{} { +func flattenHTTPMatch(apiObject *types.HttpMatch) map[string]interface{} { if apiObject == nil { return nil } @@ -789,14 +785,13 @@ func expandRuleMatch(tfMap map[string]interface{}) types.RuleMatch { apiObject := &types.RuleMatchMemberHttpMatch{} if v, ok := tfMap["http_match"].([]interface{}); ok && len(v) > 0 && v[0] != nil { - apiObject.Value = expandHttpMatch(v[0].(map[string]interface{})) - + apiObject.Value = expandHTTPMatch(v[0].(map[string]interface{})) } return apiObject } -func expandHttpMatch(tfMap map[string]interface{}) types.HttpMatch { +func expandHTTPMatch(tfMap map[string]interface{}) types.HttpMatch { apiObject := types.HttpMatch{} if v, ok := tfMap["header_matches"].([]interface{}); ok && len(v) > 0 && v != nil { diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go index 9f7c3318856..5d4a8be3bee 100644 --- a/internal/service/vpclattice/listener_rule_test.go +++ b/internal/service/vpclattice/listener_rule_test.go @@ -156,72 +156,73 @@ func TestAccVPCLatticeListenerRule_tags(t *testing.T) { func testAcclistenerRule_basic(rName string) string { return fmt.Sprintf(` - resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" - priority = 20 - match { - http_match { - - header_matches { - name = "example-header" - case_sensitive = false - - match { - exact = "example-contains" - } - } - - path_match { - case_sensitive = true - match { - prefix = "/example-path" - } - } - } - } - action { - forward { - target_groups{ - target_group_identifier = "tg-00153386728e69d10" - weight = 1 - } - target_groups{ - target_group_identifier = "tg-0fcd8d514d231b311" - weight = 2 - } - } - - } - } +resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 20 + match { + http_match { + + header_matches { + name = "example-header" + case_sensitive = false + + match { + exact = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + } + } + action { + forward { + target_groups { + target_group_identifier = "tg-00153386728e69d10" + weight = 1 + } + target_groups { + target_group_identifier = "tg-0fcd8d514d231b311" + weight = 2 + } + } + + } +} `, rName) } func testAcclistenerRule_fixedResponse(rName string) string { return fmt.Sprintf(` - resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" - priority = 10 - match { - http_match { - path_match { - case_sensitive = false - match { - exact = "/example-path" - } - } - } - } - action { - fixed_response { - status_code = 404 - } - } - } + +resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 10 + match { + http_match { + path_match { + case_sensitive = false + match { + exact = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } +} `, rName) } @@ -295,105 +296,105 @@ func testAccChecklistenerRuleDestroy(ctx context.Context) resource.TestCheckFunc func testAccListenerRule_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` - resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" - priority = 30 - match { - http_match { - path_match { - case_sensitive = false - match { - prefix = "/example-path" - } - } - } - } - action { - fixed_response { - status_code = 404 - } - } - tags = { - %[2]q = %[3]q - } - } +resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 30 + match { + http_match { + path_match { + case_sensitive = false + match { + prefix = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } + tags = { + %[2]q = %[3]q + } +} `, rName, tagKey1, tagValue1) } func testAccListenerRule_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` - resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" - priority = 30 - match { - http_match { - path_match { - case_sensitive = false - match { - prefix = "/example-path" - } - } - } - } - action { - fixed_response { - status_code = 404 - } - } - tags = { - %[2]q = %[3]q - %[4]q = %[5]q - } - } +resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 30 + match { + http_match { + path_match { + case_sensitive = false + match { + prefix = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} `, rName, tagKey1, tagValue1, tagKey2, tagValue2) } func testAcclistenerRule_methodMatch(rName string) string { return fmt.Sprintf(` - resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" - priority = 40 - match { - http_match { - - method = "POST" - - header_matches { - name = "example-header" - case_sensitive = false - - match { - contains = "example-contains" - } - } - - path_match { - case_sensitive = true - match { - prefix = "/example-path" - } - } - - } - } - action { - forward { - target_groups { - target_group_identifier = "tg-00153386728e69d10" - weight = 1 - } - target_groups { - target_group_identifier = "tg-0fcd8d514d231b311" - weight = 2 - } - } - } - } +resource "aws_vpclattice_listener_rule" "test" { + name = %q + listener_identifier = "listener-0f84b4608eae14610" + service_identifier = "svc-05cad7dcf6ee78d45" + priority = 40 + match { + http_match { + + method = "POST" + + header_matches { + name = "example-header" + case_sensitive = false + + match { + contains = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + + } + } + action { + forward { + target_groups { + target_group_identifier = "tg-00153386728e69d10" + weight = 1 + } + target_groups { + target_group_identifier = "tg-0fcd8d514d231b311" + weight = 2 + } + } + } +} `, rName) } diff --git a/website/docs/r/vpclattice_listener_rule.html.markdown b/website/docs/r/vpclattice_listener_rule.html.markdown index 8ead89f1a91..bddd8466edf 100644 --- a/website/docs/r/vpclattice_listener_rule.html.markdown +++ b/website/docs/r/vpclattice_listener_rule.html.markdown @@ -11,72 +11,73 @@ description: |- Terraform resource for managing an AWS VPC Lattice Listener Rule. ## Example Usage + ```terraform - resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = aws_vpclattice_listener.example.id - service_identifier = aws_vpclattice_service.example.id - priority = 20 - match { - http_match { - - header_matches { - name = "example-header" - case_sensitive = false - - match { - exact = "example-contains" - } - } - - path_match { - case_sensitive = true - match { - prefix = "/example-path" - } - } - } - } - action { - forward { - target_groups{ - target_group_identifier = aws_vpclattice_target_group.example.id - weight = 1 - } - target_groups{ - target_group_identifier = aws_vpclattice_target_group.example2.id - weight = 2 - } - } - - } - } +resource "aws_vpclattice_listener_rule" "test" { + name = "example" + listener_identifier = aws_vpclattice_listener.example.id + service_identifier = aws_vpclattice_service.example.id + priority = 20 + match { + http_match { + + header_matches { + name = "example-header" + case_sensitive = false + + match { + exact = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + } + } + action { + forward { + target_groups { + target_group_identifier = aws_vpclattice_target_group.example.id + weight = 1 + } + target_groups { + target_group_identifier = aws_vpclattice_target_group.example2.id + weight = 2 + } + } + + } +} ``` ### Basic Usage ```terraform - resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = aws_vpclattice_listener.example.id - service_identifier = aws_vpclattice_service.example.id - priority = 10 - match { - http_match { - path_match { - case_sensitive = false - match { - exact = "/example-path" - } - } - } - } - action { - fixed_response { - status_code = 404 - } - } - } +resource "aws_vpclattice_listener_rule" "test" { + name = "example" + listener_identifier = aws_vpclattice_listener.example.id + service_identifier = aws_vpclattice_service.example.id + priority = 10 + match { + http_match { + path_match { + case_sensitive = false + match { + exact = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } +} ``` ## Argument Reference @@ -90,46 +91,54 @@ The following arguments are required: * `name` - (Required) The name of the rule. The name must be unique within the listener. The valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen. * `priority` - (Required) The priority assigned to the rule. Each rule for a specific listener must have a unique priority. The lower the priority number the higher the priority. - The following arguments are optional: * `tags` - (Optional) Key-value mapping of resource tags. 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. action (`action`) supports the following: -* `fixed_responce` - (Optional) Describes the rule action that returns a custom HTTP response. + +* `fixed_response` - (Optional) Describes the rule action that returns a custom HTTP response. * `forward` - (Optional) The forward action. Traffic that matches the rule is forwarded to the specified target groups. -fixed responce (`fixed_responce`) supports the following: +fixed response (`fixed_response`) supports the following: + * `status_code` - (Optional) The HTTP response code. forward (`forward`) supports the following: + * `target_groups` - (Optional) The target groups. Traffic matching the rule is forwarded to the specified target groups. With forward actions, you can assign a weight that controls the prioritization and selection of each target group. This means that requests are distributed to individual target groups based on their weights. For example, if two target groups have the same weight, each target group receives half of the traffic. The default value is 1 with maximum number of 2. If only one target group is provided, there is no need to set the weight; 100% of traffic will go to that target group. action (`match`) supports the following: + * `http_match` - (Optional) The HTTP criteria that a rule must match. http match (`http_match`) supports the following: + * `header_matches` - (Optional) The header matches. Matches incoming requests with rule based on request header value before applying rule action. * `method` - (Optional) The HTTP method type. * `path_match` - (Optional) The path match. header matches (`header_matches`) supports the following: + * `case_sensitive` - (Optional) Indicates whether the match is case sensitive. Defaults to false. * `match` - (Optional) The header match type. * `name` - (Optional) The name of the header. header matches match (`match`) supports the following: + * `contains` - (Optional) Specifies a contains type match. * `exact` - (Optional) Specifies an exact type match. * `prefix` - (Optional) Specifies a prefix type match. Matches the value with the prefix. path match (`path_match`) supports the following: + * `case_sensitive` - (Optional) Indicates whether the match is case sensitive. Defaults to false. * `match` - (Optional) The header match type. path match match (`match`) supports the following: + * `exact` - (Optional) Specifies an exact type match. * `prefix` - (Optional) Specifies a prefix type match. Matches the value with the prefix. From b33ac4fb383debd828007e2c531680cc22a9d15f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 Apr 2023 17:16:26 -0400 Subject: [PATCH 12/15] r/aws_vpclattice_listener_rule: Alphabetize attributes. --- internal/service/vpclattice/listener_rule.go | 52 ++++++-------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 3cf6195fa39..f5956131c54 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -58,10 +58,6 @@ func ResourceListenerRule() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Computed: true, - }, "action": { Type: schema.TypeList, MaxItems: 1, @@ -113,6 +109,22 @@ func ResourceListenerRule() *schema.Resource { }, }, }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "listener_arn": { + Type: schema.TypeString, + Computed: true, + Optional: true, + AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, + }, + "listener_identifier": { + Type: schema.TypeString, + Computed: true, + Optional: true, + AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, + }, "match": { Type: schema.TypeList, Required: true, @@ -219,40 +231,22 @@ func ResourceListenerRule() *schema.Resource { Required: true, ValidateFunc: validation.IntBetween(1, 100), }, - "rule_id": { Type: schema.TypeString, Computed: true, }, - - "listener_arn": { - Type: schema.TypeString, - Computed: true, - Optional: true, - AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, - }, - - "listener_identifier": { - Type: schema.TypeString, - Computed: true, - Optional: true, - AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, - }, - "service_arn": { Type: schema.TypeString, Computed: true, Optional: true, AtLeastOneOf: []string{"service_arn", "service_identifier"}, }, - "service_identifier": { Type: schema.TypeString, Computed: true, Optional: true, AtLeastOneOf: []string{"service_arn", "service_identifier"}, }, - names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, @@ -470,20 +464,6 @@ func flattenRuleAction(apiObject types.RuleAction) map[string]interface{} { return tfMap } -func flattenRuleActionMemberFixedResponse(apiObject *types.RuleActionMemberFixedResponse) map[string]interface{} { - if apiObject == nil { - return nil - } - - tfMap := map[string]interface{}{} - - if v := apiObject.Value.StatusCode; v != nil { - tfMap["status_code"] = aws.ToInt32(v) - } - - return tfMap -} - func flattenForwardAction(apiObject *types.RuleActionMemberForward) map[string]interface{} { if apiObject == nil { return nil From 093aaa80991132607e9fce30333c0a1f1e9fdefe Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 Apr 2023 17:20:09 -0400 Subject: [PATCH 13/15] r/aws_vpclattice_listener_rule: Fix flattener function name duplication. --- internal/service/vpclattice/listener.go | 4 ++-- internal/service/vpclattice/listener_rule.go | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/service/vpclattice/listener.go b/internal/service/vpclattice/listener.go index 514734bfbdc..a0cd7a58054 100644 --- a/internal/service/vpclattice/listener.go +++ b/internal/service/vpclattice/listener.go @@ -335,7 +335,7 @@ func flattenListenerRuleActions(config types.RuleAction) []interface{} { switch v := config.(type) { case *types.RuleActionMemberFixedResponse: - m["fixed_response"] = flattenRuleActionMemberFixedResponse(&v.Value) + m["fixed_response"] = flattenFixedResponseAction(&v.Value) case *types.RuleActionMemberForward: m["forward"] = flattenComplexDefaultActionForward(&v.Value) } @@ -344,7 +344,7 @@ func flattenListenerRuleActions(config types.RuleAction) []interface{} { } // Flatten function for fixed_response action -func flattenRuleActionMemberFixedResponse(response *types.FixedResponseAction) []interface{} { +func flattenFixedResponseAction(response *types.FixedResponseAction) []interface{} { tfMap := map[string]interface{}{} if v := response.StatusCode; v != nil { diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index f5956131c54..9c9b16f14f2 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -464,6 +464,20 @@ func flattenRuleAction(apiObject types.RuleAction) map[string]interface{} { return tfMap } +func flattenRuleActionMemberFixedResponse(apiObject *types.RuleActionMemberFixedResponse) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Value.StatusCode; v != nil { + tfMap["status_code"] = aws.ToInt32(v) + } + + return tfMap +} + func flattenForwardAction(apiObject *types.RuleActionMemberForward) map[string]interface{} { if apiObject == nil { return nil From 898e3c793597464dbdeb3d97e10f44a6dc12e643 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 Apr 2023 08:43:23 -0400 Subject: [PATCH 14/15] r/aws_vpclattice_listener_rule: Fixup acceptance tests. --- .../service/vpclattice/listener_rule_test.go | 232 ++++++++++-------- internal/service/vpclattice/listener_test.go | 2 - 2 files changed, 128 insertions(+), 106 deletions(-) diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go index 5d4a8be3bee..40b893d5c0e 100644 --- a/internal/service/vpclattice/listener_rule_test.go +++ b/internal/service/vpclattice/listener_rule_test.go @@ -37,7 +37,7 @@ func TestAccVPCLatticeListenerRule_basic(t *testing.T) { CheckDestroy: testAccChecklistenerRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAcclistenerRule_basic(rName), + Config: testAccListenerRuleConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), resource.TestCheckResourceAttr(resourceName, "priority", "20"), @@ -70,7 +70,7 @@ func TestAccVPCLatticeListenerRule_fixedResponse(t *testing.T) { CheckDestroy: testAccChecklistenerRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAcclistenerRule_fixedResponse(rName), + Config: testAccListenerRuleConfig_fixedResponse(rName), Check: resource.ComposeTestCheckFunc( testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), resource.TestCheckResourceAttr(resourceName, "name", rName), @@ -100,7 +100,7 @@ func TestAccVPCLatticeListenerRule_methodMatch(t *testing.T) { CheckDestroy: testAccChecklistenerRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAcclistenerRule_methodMatch(rName), + Config: testAccListenerRuleConfig_methodMatch(rName), Check: resource.ComposeTestCheckFunc( testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), resource.TestCheckResourceAttr(resourceName, "name", rName), @@ -129,7 +129,7 @@ func TestAccVPCLatticeListenerRule_tags(t *testing.T) { CheckDestroy: testAccChecklistenerRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccListenerRule_tags1(rName, "key1", "value1"), + Config: testAccListenerRuleConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -142,7 +142,7 @@ func TestAccVPCLatticeListenerRule_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccListenerRule_tags2(rName, "key1", "value1updated", "key2", "value2"), + Config: testAccListenerRuleConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckListenerRuleExists(ctx, resourceName, &listenerRule), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -154,78 +154,6 @@ func TestAccVPCLatticeListenerRule_tags(t *testing.T) { }) } -func testAcclistenerRule_basic(rName string) string { - return fmt.Sprintf(` -resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" - priority = 20 - match { - http_match { - - header_matches { - name = "example-header" - case_sensitive = false - - match { - exact = "example-contains" - } - } - - path_match { - case_sensitive = true - match { - prefix = "/example-path" - } - } - } - } - action { - forward { - target_groups { - target_group_identifier = "tg-00153386728e69d10" - weight = 1 - } - target_groups { - target_group_identifier = "tg-0fcd8d514d231b311" - weight = 2 - } - } - - } -} -`, rName) -} - -func testAcclistenerRule_fixedResponse(rName string) string { - return fmt.Sprintf(` - - -resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" - priority = 10 - match { - http_match { - path_match { - case_sensitive = false - match { - exact = "/example-path" - } - } - } - } - action { - fixed_response { - status_code = 404 - } - } -} -`, rName) -} - func testAccCheckListenerRuleExists(ctx context.Context, name string, rule *vpclattice.GetRuleOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -266,13 +194,8 @@ func testAccChecklistenerRuleDestroy(ctx context.Context) resource.TestCheckFunc continue } - listenerRuleResource, ok := s.RootModule().Resources["aws_vpclattice_listener_rule.test"] - if !ok { - return fmt.Errorf("Not found: %s", "aws_vpclattice_listener_rule.test") - } - - listenerIdentifier := listenerRuleResource.Primary.Attributes["listener_identifier"] - serviceIdentifier := listenerRuleResource.Primary.Attributes["service_identifier"] + listenerIdentifier := rs.Primary.Attributes["listener_identifier"] + serviceIdentifier := rs.Primary.Attributes["service_identifier"] _, err := conn.GetRule(ctx, &vpclattice.GetRuleInput{ RuleIdentifier: aws.String(rs.Primary.Attributes["arn"]), @@ -294,12 +217,113 @@ func testAccChecklistenerRuleDestroy(ctx context.Context) resource.TestCheckFunc } } -func testAccListenerRule_tags1(rName, tagKey1, tagValue1 string) string { - return fmt.Sprintf(` +func testAccListenerRuleConfig_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` +resource "aws_vpclattice_service" "test" { + name = %[1]q +} + +resource "aws_vpclattice_target_group" "test" { + count = 2 + + name = "%[1]s-${count.index}" + type = "INSTANCE" + + config { + port = 80 + protocol = "HTTP" + vpc_identifier = aws_vpc.test.id + } +} + +resource "aws_vpclattice_listener" "test" { + name = %[1]q + protocol = "HTTP" + service_identifier = aws_vpclattice_service.test.id + default_action { + fixed_response { + status_code = 404 + } + } +} +`, rName)) +} + +func testAccListenerRuleConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` +resource "aws_vpclattice_listener_rule" "test" { + name = %[1]q + listener_identifier = aws_vpclattice_listener.test.id + service_identifier = aws_vpclattice_service.test.id + priority = 20 + match { + http_match { + + header_matches { + name = "example-header" + case_sensitive = false + + match { + exact = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + } + } + action { + forward { + target_groups { + target_group_identifier = aws_vpclattice_target_group.test[0].id + weight = 1 + } + target_groups { + target_group_identifier = aws_vpclattice_target_group.test[1].id + weight = 2 + } + } + } +} +`, rName)) +} + +func testAccListenerRuleConfig_fixedResponse(rName string) string { + return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` +resource "aws_vpclattice_listener_rule" "test" { + name = %[1]q + listener_identifier = aws_vpclattice_listener.test.id + service_identifier = aws_vpclattice_service.test.id + priority = 10 + match { + http_match { + path_match { + case_sensitive = false + match { + exact = "/example-path" + } + } + } + } + action { + fixed_response { + status_code = 404 + } + } +} +`, rName)) +} + +func testAccListenerRuleConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" + name = %[1]q + listener_identifier = aws_vpclattice_listener.test.id + service_identifier = aws_vpclattice_service.test.id priority = 30 match { http_match { @@ -320,15 +344,15 @@ resource "aws_vpclattice_listener_rule" "test" { %[2]q = %[3]q } } -`, rName, tagKey1, tagValue1) +`, rName, tagKey1, tagValue1)) } -func testAccListenerRule_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return fmt.Sprintf(` +func testAccListenerRuleConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" + name = %[1]q + listener_identifier = aws_vpclattice_listener.test.id + service_identifier = aws_vpclattice_service.test.id priority = 30 match { http_match { @@ -350,15 +374,15 @@ resource "aws_vpclattice_listener_rule" "test" { %[4]q = %[5]q } } -`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } -func testAcclistenerRule_methodMatch(rName string) string { - return fmt.Sprintf(` +func testAccListenerRuleConfig_methodMatch(rName string) string { + return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { - name = %q - listener_identifier = "listener-0f84b4608eae14610" - service_identifier = "svc-05cad7dcf6ee78d45" + name = %[1]q + listener_identifier = aws_vpclattice_listener.test.id + service_identifier = aws_vpclattice_service.test.id priority = 40 match { http_match { @@ -386,15 +410,15 @@ resource "aws_vpclattice_listener_rule" "test" { action { forward { target_groups { - target_group_identifier = "tg-00153386728e69d10" + target_group_identifier = aws_vpclattice_target_group.test[0].id weight = 1 } target_groups { - target_group_identifier = "tg-0fcd8d514d231b311" + target_group_identifier = aws_vpclattice_target_group.test[1].id weight = 2 } } } } -`, rName) +`, rName)) } diff --git a/internal/service/vpclattice/listener_test.go b/internal/service/vpclattice/listener_test.go index e7127cf4d6b..b98bd80663c 100644 --- a/internal/service/vpclattice/listener_test.go +++ b/internal/service/vpclattice/listener_test.go @@ -517,8 +517,6 @@ resource "aws_vpclattice_target_group" "test" { vpc_identifier = aws_vpc.test.id } } - - `, rName)) } From 70762e234b1d9a15add69cd1f47cd827ce9506b6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 Apr 2023 09:06:56 -0400 Subject: [PATCH 15/15] r/aws_vpclattice_listener_rule: Remove 'service_arn' and 'listener_arn' -- single ID fields, ForceNew. --- internal/service/vpclattice/listener_rule.go | 62 ++++--------------- .../service/vpclattice/listener_rule_test.go | 10 +-- .../r/vpclattice_listener_rule.html.markdown | 4 +- 3 files changed, 20 insertions(+), 56 deletions(-) diff --git a/internal/service/vpclattice/listener_rule.go b/internal/service/vpclattice/listener_rule.go index 9c9b16f14f2..dccc5f8f03a 100644 --- a/internal/service/vpclattice/listener_rule.go +++ b/internal/service/vpclattice/listener_rule.go @@ -113,17 +113,10 @@ func ResourceListenerRule() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "listener_arn": { - Type: schema.TypeString, - Computed: true, - Optional: true, - AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, - }, "listener_identifier": { - Type: schema.TypeString, - Computed: true, - Optional: true, - AtLeastOneOf: []string{"listener_arn", "listener_identifier"}, + Type: schema.TypeString, + Required: true, + ForceNew: true, }, "match": { Type: schema.TypeList, @@ -235,17 +228,10 @@ func ResourceListenerRule() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "service_arn": { - Type: schema.TypeString, - Computed: true, - Optional: true, - AtLeastOneOf: []string{"service_arn", "service_identifier"}, - }, "service_identifier": { - Type: schema.TypeString, - Computed: true, - Optional: true, - AtLeastOneOf: []string{"service_arn", "service_identifier"}, + Type: schema.TypeString, + Required: true, + ForceNew: true, }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), @@ -266,41 +252,19 @@ func resourceListenerRuleCreate(ctx context.Context, d *schema.ResourceData, met name := d.Get("name").(string) in := &vpclattice.CreateRuleInput{ - ClientToken: aws.String(id.UniqueId()), - Name: aws.String(name), - Action: expandRuleAction(d.Get("action").([]interface{})[0].(map[string]interface{})), - Match: expandRuleMatch(d.Get("match").([]interface{})[0].(map[string]interface{})), - Tags: GetTagsIn(ctx), + Action: expandRuleAction(d.Get("action").([]interface{})[0].(map[string]interface{})), + ClientToken: aws.String(id.UniqueId()), + ListenerIdentifier: aws.String(d.Get("listener_identifier").(string)), + Match: expandRuleMatch(d.Get("match").([]interface{})[0].(map[string]interface{})), + Name: aws.String(name), + ServiceIdentifier: aws.String(d.Get("service_identifier").(string)), + Tags: GetTagsIn(ctx), } if v, ok := d.GetOk("priority"); ok { in.Priority = aws.Int32(int32(v.(int))) } - if v, ok := d.GetOk("service_identifier"); ok { - in.ServiceIdentifier = aws.String(v.(string)) - } - - if v, ok := d.GetOk("service_arn"); ok { - in.ServiceIdentifier = aws.String(v.(string)) - } - - if in.ServiceIdentifier == nil { - return diag.FromErr(fmt.Errorf("must specify either service_arn or service_identifier")) - } - - if v, ok := d.GetOk("listener_identifier"); ok { - in.ListenerIdentifier = aws.String(v.(string)) - } - - if v, ok := d.GetOk("listener_arn"); ok { - in.ListenerIdentifier = aws.String(v.(string)) - } - - if in.ListenerIdentifier == nil { - return diag.FromErr(fmt.Errorf("must specify either listener_arn or listener_identifier")) - } - out, err := conn.CreateRule(ctx, in) if err != nil { return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameListenerRule, name, err) diff --git a/internal/service/vpclattice/listener_rule_test.go b/internal/service/vpclattice/listener_rule_test.go index 40b893d5c0e..6196f45851e 100644 --- a/internal/service/vpclattice/listener_rule_test.go +++ b/internal/service/vpclattice/listener_rule_test.go @@ -253,7 +253,7 @@ func testAccListenerRuleConfig_basic(rName string) string { return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { name = %[1]q - listener_identifier = aws_vpclattice_listener.test.id + listener_identifier = aws_vpclattice_listener.test.listener_id service_identifier = aws_vpclattice_service.test.id priority = 20 match { @@ -296,7 +296,7 @@ func testAccListenerRuleConfig_fixedResponse(rName string) string { return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { name = %[1]q - listener_identifier = aws_vpclattice_listener.test.id + listener_identifier = aws_vpclattice_listener.test.listener_id service_identifier = aws_vpclattice_service.test.id priority = 10 match { @@ -322,7 +322,7 @@ func testAccListenerRuleConfig_tags1(rName, tagKey1, tagValue1 string) string { return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { name = %[1]q - listener_identifier = aws_vpclattice_listener.test.id + listener_identifier = aws_vpclattice_listener.test.listener_id service_identifier = aws_vpclattice_service.test.id priority = 30 match { @@ -351,7 +351,7 @@ func testAccListenerRuleConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValu return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { name = %[1]q - listener_identifier = aws_vpclattice_listener.test.id + listener_identifier = aws_vpclattice_listener.test.listener_id service_identifier = aws_vpclattice_service.test.id priority = 30 match { @@ -381,7 +381,7 @@ func testAccListenerRuleConfig_methodMatch(rName string) string { return acctest.ConfigCompose(testAccListenerRuleConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_listener_rule" "test" { name = %[1]q - listener_identifier = aws_vpclattice_listener.test.id + listener_identifier = aws_vpclattice_listener.test.listener_id service_identifier = aws_vpclattice_service.test.id priority = 40 match { diff --git a/website/docs/r/vpclattice_listener_rule.html.markdown b/website/docs/r/vpclattice_listener_rule.html.markdown index bddd8466edf..77b968dd699 100644 --- a/website/docs/r/vpclattice_listener_rule.html.markdown +++ b/website/docs/r/vpclattice_listener_rule.html.markdown @@ -15,7 +15,7 @@ Terraform resource for managing an AWS VPC Lattice Listener Rule. ```terraform resource "aws_vpclattice_listener_rule" "test" { name = "example" - listener_identifier = aws_vpclattice_listener.example.id + listener_identifier = aws_vpclattice_listener.example.listener_id service_identifier = aws_vpclattice_service.example.id priority = 20 match { @@ -59,7 +59,7 @@ resource "aws_vpclattice_listener_rule" "test" { ```terraform resource "aws_vpclattice_listener_rule" "test" { name = "example" - listener_identifier = aws_vpclattice_listener.example.id + listener_identifier = aws_vpclattice_listener.example.listener_id service_identifier = aws_vpclattice_service.example.id priority = 10 match {