[アップデート] AWS Transfer Family SFTP コネクタがSFTPサーバ上のファイル削除・移動操作をサポートしました
初めに
先日SFTP コネクタでSFTPサーバ上のファイルの削除・移動(名前変更を含む)操作の機能追加のアップデートがありました。
SFTP コネクタはSFTPサーバとS3の間のファイル転送を行えるサービスで、本アップデート以前ではS3とSFTPサーバ間のファイルの転送(GET&PUT)及びSFTPサーバ側のファイルの一覧取得操作(LIST)をサポートしていました。
今回のアップデートではこれに加えてSFTPサーバ上のファイルの削除及び移動がサポートされるようになったため、
ファイル転送後は送信元にファイルを残す必要がない場合はその削除、処理済みファイル置き場のような場所に移動といった後処理までAWS APIで完結可能となります。
実際に使ってみる
SFTPサーバ上のcur
ディレクトリの中身をS3バケットに転送し、転送が完了したファイルはcur
からarc
ディレクトリに移動という操作をAWSサービス上で完結させてみます。
少しごちゃっとしてますが構成イメージは以下の通りです。起動はCLIでstart-directory-listing
を実行する形で発火させます。
ファイル移動が本趣旨ですが、ファイル取得・設置・一覧取得、イベント駆動とここまでリリースされてきた機能ほぼ全盛です。
本当はLambda関数使わずにもStep Functionsで済ませたかったのですが、本記事執筆時点(2025/04/10)でまだStep Functions側のアクション一覧になかったのでLambda関数で処理を呼び出してます。Step Functions側が対応すればあえてLambda関数を使う必要はないです。
※ 直打ちしたけど当然エラー
実装
今回はAWS SAMで実装しています。
SAMテンプレート
SFTPコネクタは含まれないので別途セットアップしてください。
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Parameters:
# c-xxxxの値を設定
ConnectorId:
Type: String
Resources:
DataBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub sftp-connector-mvrm-example-${AWS::AccountId}
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
#---------------------------------------------
#---- List Complete Event Receive Machine
#---------------------------------------------
TransferExecutor:
Type: AWS::Serverless::StateMachine
Properties:
DefinitionUri: statemachine/transfer-executor.json
DefinitionSubstitutions:
ConnectorId: !Ref ConnectorId
OutputBucket: !Ref DataBucket
Policies:
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action: s3:GetObject
Resource:
- !Sub ${DataBucket.Arn}/list-result/*
- Effect: Allow
Action: transfer:StartFileTransfer
Resource:
- !Sub arn:aws:transfer:${AWS::Region}:${AWS::AccountId}:connector/${ConnectorId}
Events:
ListCompleteEvent:
Type: EventBridgeRule
Properties:
RuleName: sftp-connector-listing-complete-event-rule
Target:
Id: complete-receive-machine
Pattern:
source:
- "aws.transfer"
detail-type:
- "SFTP Connector Directory Listing Completed"
resources:
- !Sub arn:aws:transfer:${AWS::Region}:${AWS::AccountId}:connector/${ConnectorId}
#---------------------------------------------
#---- File Send Complete Event Receive Function
#---------------------------------------------
MoveFileExecutor:
Type: AWS::Serverless::Function
Properties:
CodeUri: functions/move_file
Handler: app.lambda_handler
Runtime: python3.12
MemorySize: 256
Architectures:
- arm64
Policies:
- AWSLambdaExecute
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- transfer:StartRemoteMove
#後で使うのでついでに
- transfer:StartRemoteDelete
Resource:
- !Sub arn:aws:transfer:${AWS::Region}:${AWS::AccountId}:connector/${ConnectorId}
Events:
SendFileComplete:
Type: EventBridgeRule
Properties:
RuleName: sftp-connector-send-complete-event-rule
Target:
Id: complete-send-machine
Pattern:
source:
- "aws.transfer"
detail-type:
- "SFTP Connector File Retrieve Completed"
resources:
- !Sub arn:aws:transfer:${AWS::Region}:${AWS::AccountId}:connector/${ConnectorId}
ファイル転送用ステートマシン定義
ファイル一覧機能実装時の記事記載のものをそのまま流用しています。
{
"Comment": "SFTP connector list complaete receiver",
"States": {
"GetObject": {
"Type": "Task",
"Parameters": {
"Bucket.$": "$.detail.output-file-location.bucket",
"Key.$": "$.detail.output-file-location.key"
},
"Resource": "arn:aws:states:::aws-sdk:s3:getObject",
"Next": "Map",
"ResultSelector": {
"Body.$": "States.StringToJson($.Body)"
}
},
"Map": {
"Type": "Map",
"ItemProcessor": {
"ProcessorConfig": {
"Mode": "INLINE"
},
"StartAt": "StartFileTransfer",
"States": {
"StartFileTransfer": {
"Type": "Task",
"End": true,
"Parameters": {
"ConnectorId": "${ConnectorId}",
"RetrieveFilePaths.$": "States.Array($.filePath)",
"LocalDirectoryPath": "/${OutputBucket}/output"
},
"Resource": "arn:aws:states:::aws-sdk:transfer:startFileTransfer"
}
}
},
"End": true,
"ItemsPath": "$.Body.files"
}
},
"StartAt": "GetObject"
}
ファイル移動処理用Lambda関数
import boto3
transfer = boto3.client("transfer")
DST_PATH = "/home/sftpuser/arc/"
def lambda_handler(event, context):
transfer.start_remote_move(
ConnectorId=event["detail"]["connector-id"],
SourcePath= event["detail"]["file-path"],
TargetPath= DST_PATH + event["detail"]["file-path"].split("/")[-1]
)
return {
"statusCode": 200,
"body": ""
}
本記事執筆時点で組み込みのboto3がstart_remote_move()
が未実装のバージョンっぽかったのでrequirements.txt
にboto3を記載して入れ込んでます。
boto3==1.37.31
実行(移動処理)
動かしてみます。実行前ファイル配置は以下の通りです。
$ tree cur arc
cur
├── sec1.csv
├── sec2.csv
├── sec3.csv
├── sec4.csv
└── sec5.csv
arc
一覧取得を実行して発火させます。
% aws transfer start-directory-listing --connector-id c-xxxxxx --remote-directory-path /home/sftpuser/cur --output-directory-path /sftp-connector-mvrm-example-xxxxxx/list-result
{
"ListingId": "0bb18d9c-4dae-4960-8a65-97b907d9c01a",
"OutputFileName": "c-xxxxxx-0bb18d9c-4dae-4960-8a65-97b907d9c01a.json"
}
そうすると転送用ステートマシンが稼働し、S3上にファイルが設置されます。
転送完了するとファイル単位でLambda関数が動き出しファイルが移動されます。
$ tree cur arc
cur
arc
├── sec1.csv
├── sec2.csv
├── sec3.csv
├── sec4.csv
└── sec5.csv
移動時のSFTPコネクタのログは以下のような感じです。ファイルサイズが同じくらいな関係でRETRIEVE
が全部出た後MOVE
という形にはなってますがイベント自体はファイル単位で行われてるはずなので実運用だともう少しごちゃごちゃする形になると思います。
MOVE
操作単体のログは以下の通りです。
{
"operation": "MOVE",
"timestamp": "2025-04-10T08:17:13.691383393Z",
"connector-id": "c-xxxxx",
"url": "sftp://xxxxx",
"status-code": "COMPLETED",
"start-time": "2025-04-10T08:17:13.675527010Z",
"end-time": "2025-04-10T08:17:13.676736854Z",
"account-id": "xxxxx",
"connector-arn": "arn:aws:transfer:ap-northeast-1:xxxxx:connector/c-xxxxx",
"move-id": "a7cbb745-5412-4a85-a93f-54debcd0a0a2",
"move-source-path": "/home/sftpuser/cur/sec5.csv",
"move-target-path": "/home/sftpuser/arc/sec5.csv"
}
start_remote_move()
の返却値を別途出力してみましたが以下の通りです。
{'MoveId': 'cbfe0eaf-c642-4973-83c2-03698ef9101b', 'ResponseMetadata': {'RequestId': '20b70db4-706f-4cec-a89e-75386fe7a843', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Thu, 10 Apr 2025 08:24:52 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '49', 'connection': 'keep-alive', 'x-amzn-requestid': '20b70db4-706f-4cec-a89e-75386fe7a843'}, 'RetryAttempts': 0}}
執筆時点で英語版ドキュメント側にも移動完了のイベントはなかったので同期処理?と思いましたがマネジメントコンソール上でEventBridgeのサンプルイベントを見たら移動完了/失敗イベントがあったのでドキュメントの更新が追いついてないだけでイベントはありそうです。なのでやはり非同期処理でしょうか。
実行(削除処理)
ついでに削除処理も見ておきましょう。
先ほどのLambda関数の内容を変更しcur
からarc
に移動ではなくファイル削除にします。
import boto3
transfer = boto3.client("transfer")
def lambda_handler(event, context):
res = transfer.start_remote_delete(
ConnectorId=event["detail"]["connector-id"],
DeletePath= event["detail"]["file-path"],
)
print(res)
return {
"statusCode": 200,
"body": ""
}
ファイルの配置はこんな形にしておきます。
$ tree
.
├── arc
│ ├── sec1.csv
│ ├── sec2.csv
│ ├── sec3.csv
│ ├── sec4.csv
│ └── sec5.csv
├── cur
│ ├── sec1.csv
│ ├── sec2.csv
│ ├── sec3.csv
│ ├── sec4.csv
│ └── sec5.csv
これを先ほど同様に発火させると今度はファイル移動ではなく削除が行われます。
tree
.
├── arc
│ ├── sec1.csv
│ ├── sec2.csv
│ ├── sec3.csv
│ ├── sec4.csv
│ └── sec5.csv
├── cur
この時のSFTPコネクタは以下の通りです(1件だけ抜粋)。
{
"operation": "DELETE",
"timestamp": "2025-04-10T08:53:17.886541341Z",
"connector-id": "c-xxxxx",
"url": "sftp://xxxxxx",
"status-code": "COMPLETED",
"start-time": "2025-04-10T08:53:17.865823935Z",
"end-time": "2025-04-10T08:53:17.867447390Z",
"account-id": "xxxxx",
"connector-arn": "arn:aws:transfer:ap-northeast-1:xxxxx:connector/c-xxxxx",
"delete-id": "94691bdc-a682-42bf-abe2-b21bd76a76e5",
"delete-path": "/home/sftpuser/cur/sec2.csv"
}
またstart_remote_delete()
のレスポンスは以下の通りです。
{'DeleteId': 'cf06b168-aa33-4499-9e4f-fd6a7807d82c', 'ResponseMetadata': {'RequestId': 'c4a5b9b5-2d63-4f0b-b6ba-e00e8fcf3c2e', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Thu, 10 Apr 2025 08:53:16 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '51', 'connection': 'keep-alive', 'x-amzn-requestid': 'c4a5b9b5-2d63-4f0b-b6ba-e00e8fcf3c2e'}, 'RetryAttempts': 0}}
終わりに
今回はSFTPコネクタの新しい機能であるSFTPサーバ上のファイル移動・削除を転送処理の後処理として使ってみました。
転送後は元サーバに不要だからファイル削除したい場合別途OS上でゴニョゴニョしないといけなかったのがある程度であればAWSレイヤーから対応できるのは結構嬉しいのではないでしょうか。