Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changelog/45314.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/aws_transfer_connector: Add `egress_config` argument to support VPC Lattice connectivity for SFTP connectors
```

```release-note:enhancement
resource/aws_transfer_connector: Make `url` argument optional to support VPC Lattice connectors
```

```release-note:enhancement
data-source/aws_transfer_connector: Add `egress_config` attribute to expose VPC Lattice connectivity configuration
```
250 changes: 248 additions & 2 deletions internal/service/transfer/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ package transfer

import (
"context"
"fmt"
"log"
"time"

"github.com/YakDriver/regexache"
"github.com/aws/aws-sdk-go-v2/aws"
Expand Down Expand Up @@ -38,6 +40,12 @@ func resourceConnector() *schema.Resource {
StateContext: schema.ImportStatePassthroughContext,
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Update: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},

Schema: map[string]*schema.Schema{
"access_role": {
Type: schema.TypeString,
Expand Down Expand Up @@ -134,11 +142,39 @@ func resourceConnector() *schema.Resource {
},
},
},
"egress_config": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"vpc_lattice": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"resource_configuration_arn": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 2048),
},
"port_number": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(1, 65535),
},
},
},
},
},
},
},
names.AttrTags: tftags.TagsSchema(),
names.AttrTagsAll: tftags.TagsSchemaComputed(),
names.AttrURL: {
Type: schema.TypeString,
Required: true,
Optional: true,
},
},
}
Expand All @@ -151,13 +187,20 @@ func resourceConnectorCreate(ctx context.Context, d *schema.ResourceData, meta a
input := &transfer.CreateConnectorInput{
AccessRole: aws.String(d.Get("access_role").(string)),
Tags: getTagsIn(ctx),
Url: aws.String(d.Get(names.AttrURL).(string)),
}

if v, ok := d.GetOk(names.AttrURL); ok {
input.Url = aws.String(v.(string))
}

if v, ok := d.GetOk("as2_config"); ok {
input.As2Config = expandAs2ConnectorConfig(v.([]any))
}

if v, ok := d.GetOk("egress_config"); ok {
input.EgressConfig = expandConnectorEgressConfig(v.([]any))
}

if v, ok := d.GetOk("logging_role"); ok {
input.LoggingRole = aws.String(v.(string))
}
Expand All @@ -178,6 +221,10 @@ func resourceConnectorCreate(ctx context.Context, d *schema.ResourceData, meta a

d.SetId(aws.ToString(output.ConnectorId))

if _, err := waitConnectorCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Transfer Connector (%s) create: %s", d.Id(), err)
}

return append(diags, resourceConnectorRead(ctx, d, meta)...)
}

Expand All @@ -203,6 +250,9 @@ func resourceConnectorRead(ctx context.Context, d *schema.ResourceData, meta any
return sdkdiag.AppendErrorf(diags, "setting as2_config: %s", err)
}
d.Set("connector_id", output.ConnectorId)
if err := d.Set("egress_config", flattenConnectorEgressConfig(output.EgressConfig)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting egress_config: %s", err)
}
d.Set("logging_role", output.LoggingRole)
d.Set("security_policy_name", output.SecurityPolicyName)
if err := d.Set("sftp_config", flattenSftpConnectorConfig(output.SftpConfig)); err != nil {
Expand Down Expand Up @@ -232,6 +282,10 @@ func resourceConnectorUpdate(ctx context.Context, d *schema.ResourceData, meta a
input.As2Config = expandAs2ConnectorConfig(d.Get("as2_config").([]any))
}

if d.HasChange("egress_config") {
input.EgressConfig = expandUpdateConnectorEgressConfig(d.Get("egress_config").([]any))
}

if d.HasChange("logging_role") {
input.LoggingRole = aws.String(d.Get("logging_role").(string))
}
Expand All @@ -253,6 +307,10 @@ func resourceConnectorUpdate(ctx context.Context, d *schema.ResourceData, meta a
if err != nil {
return sdkdiag.AppendErrorf(diags, "updating Transfer Connector (%s): %s", d.Id(), err)
}

if _, err := waitConnectorUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Transfer Connector (%s) update: %s", d.Id(), err)
}
}

return append(diags, resourceConnectorRead(ctx, d, meta)...)
Expand All @@ -276,6 +334,10 @@ func resourceConnectorDelete(ctx context.Context, d *schema.ResourceData, meta a
return sdkdiag.AppendErrorf(diags, "deleting Transfer Connector (%s): %s", d.Id(), err)
}

if _, err := waitConnectorDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Transfer Connector (%s) delete: %s", d.Id(), err)
}

return diags
}

Expand Down Expand Up @@ -383,3 +445,187 @@ func flattenSftpConnectorConfig(apiObject *awstypes.SftpConnectorConfig) []any {

return []any{tfMap}
}

func expandConnectorEgressConfig(tfList []any) awstypes.ConnectorEgressConfig {
if len(tfList) < 1 || tfList[0] == nil {
return nil
}

tfMap := tfList[0].(map[string]any)

if v, ok := tfMap["vpc_lattice"].([]any); ok && len(v) > 0 {
return &awstypes.ConnectorEgressConfigMemberVpcLattice{
Value: *expandConnectorVPCLatticeEgressConfig(v),
}
}

return nil
}

func expandUpdateConnectorEgressConfig(tfList []any) awstypes.UpdateConnectorEgressConfig {
if len(tfList) < 1 || tfList[0] == nil {
return nil
}

tfMap := tfList[0].(map[string]any)

if v, ok := tfMap["vpc_lattice"].([]any); ok && len(v) > 0 {
return &awstypes.UpdateConnectorEgressConfigMemberVpcLattice{
Value: *expandUpdateConnectorVPCLatticeEgressConfig(v),
}
}

return nil
}

func expandConnectorVPCLatticeEgressConfig(tfList []any) *awstypes.ConnectorVpcLatticeEgressConfig {
if len(tfList) < 1 || tfList[0] == nil {
return nil
}

tfMap := tfList[0].(map[string]any)

apiObject := &awstypes.ConnectorVpcLatticeEgressConfig{
ResourceConfigurationArn: aws.String(tfMap["resource_configuration_arn"].(string)),
}

if v, ok := tfMap["port_number"].(int); ok && v > 0 {
apiObject.PortNumber = aws.Int32(int32(v))
}

return apiObject
}

func expandUpdateConnectorVPCLatticeEgressConfig(tfList []any) *awstypes.UpdateConnectorVpcLatticeEgressConfig {
if len(tfList) < 1 || tfList[0] == nil {
return nil
}

tfMap := tfList[0].(map[string]any)

apiObject := &awstypes.UpdateConnectorVpcLatticeEgressConfig{
ResourceConfigurationArn: aws.String(tfMap["resource_configuration_arn"].(string)),
}

if v, ok := tfMap["port_number"].(int); ok && v > 0 {
apiObject.PortNumber = aws.Int32(int32(v))
}

return apiObject
}

func flattenConnectorEgressConfig(apiObject awstypes.DescribedConnectorEgressConfig) []any {
if apiObject == nil {
return nil
}

tfMap := map[string]any{}

switch v := apiObject.(type) {
case *awstypes.DescribedConnectorEgressConfigMemberVpcLattice:
tfMap["vpc_lattice"] = flattenDescribedConnectorVPCLatticeEgressConfig(&v.Value)
}

if len(tfMap) == 0 {
return nil
}

return []any{tfMap}
}

func flattenDescribedConnectorVPCLatticeEgressConfig(apiObject *awstypes.DescribedConnectorVpcLatticeEgressConfig) []any {
if apiObject == nil {
return nil
}

tfMap := map[string]any{}

if v := apiObject.ResourceConfigurationArn; v != nil {
tfMap["resource_configuration_arn"] = aws.ToString(v)
}

if v := apiObject.PortNumber; v != nil {
tfMap["port_number"] = aws.ToInt32(v)
}

return []any{tfMap}
}

func statusConnector(ctx context.Context, conn *transfer.Client, id string) retry.StateRefreshFunc {
return func() (any, string, error) {
output, err := findConnectorByID(ctx, conn, id)

if tfresource.NotFound(err) {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

return output, string(output.Status), nil
}
}

func waitConnectorCreated(ctx context.Context, conn *transfer.Client, id string, timeout time.Duration) (*awstypes.DescribedConnector, error) {
stateConf := &retry.StateChangeConf{
Pending: enum.Slice(awstypes.ConnectorStatusPending),
Target: enum.Slice(awstypes.ConnectorStatusActive),
Refresh: statusConnector(ctx, conn, id),
Timeout: timeout,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)

if output, ok := outputRaw.(*awstypes.DescribedConnector); ok {
if output.Status == awstypes.ConnectorStatusErrored {
tfresource.SetLastError(err, fmt.Errorf("%s", aws.ToString(output.ErrorMessage)))
}

return output, err
}

return nil, err
}

func waitConnectorUpdated(ctx context.Context, conn *transfer.Client, id string, timeout time.Duration) (*awstypes.DescribedConnector, error) {
stateConf := &retry.StateChangeConf{
Pending: enum.Slice(awstypes.ConnectorStatusPending),
Target: enum.Slice(awstypes.ConnectorStatusActive),
Refresh: statusConnector(ctx, conn, id),
Timeout: timeout,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)

if output, ok := outputRaw.(*awstypes.DescribedConnector); ok {
if output.Status == awstypes.ConnectorStatusErrored {
tfresource.SetLastError(err, fmt.Errorf("%s", aws.ToString(output.ErrorMessage)))
}

return output, err
}

return nil, err
}

func waitConnectorDeleted(ctx context.Context, conn *transfer.Client, id string, timeout time.Duration) (*awstypes.DescribedConnector, error) {
stateConf := &retry.StateChangeConf{
Pending: enum.Slice(awstypes.ConnectorStatusActive, awstypes.ConnectorStatusPending),
Target: []string{},
Refresh: statusConnector(ctx, conn, id),
Timeout: timeout,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)

if output, ok := outputRaw.(*awstypes.DescribedConnector); ok {
if output.Status == awstypes.ConnectorStatusErrored {
tfresource.SetLastError(err, fmt.Errorf("%s", aws.ToString(output.ErrorMessage)))
}

return output, err
}

return nil, err
}
Loading
Loading