初めに
先日Transfer FamilyのSFTPコネクタにリモートサーバからファイル一覧を取得する機能が追加されました。
従来ではSFTPコネクタはあくまでS3とSFTPサーバ間のファイルの転送のみをサポートしており、そのファイルの転送の指定にもワイルドーカードや正規表現の利用もできず完全一致を行うようにファイル名を指定する必要があった為、ファイル名が動的な値の場合は何かしら別の手段で取得する必要がありました。
転送元がS3の場合はListObjectsで取得できるためStep Functionsと組み合わせシンプルに実装ができるものの、転送元がSFTPサーバとなるとOS上のレイヤーなこともありAWSレイヤーからどうにかしようとする場合はシンプルに実装は難しいものとなっておりました。
今回のアップデートで追加された機能により転送元がSFTPサーバであってもシンプルにその一覧が取得できるようになったため試してみます。
実際に取得してみる
StartDirectoryListing
API、CLIの場合はaws transfer start-directory-listing
を利用することで取得可能です。
方式としてはシンプルに一覧がAPIの返却値として返ってくるわけではなくコネクタに指定されたバケット上に設置される形となります。
パラメータとしてMaxItems
(最大取得数)というものもあるためターゲット層的にファイル数が膨大等でファイルとした方が都合が良かったのでしょうか。
転送元のサーバには以下のようにファイルを設置してあります。
$ tree /home/sftpuser
.
├── sec1.csv
├── sec2.csv
├── sec3.csv
├── sec4.csv
├── sec5.csv
├── sub
│ └── sub-file.txt
├── test1.txt
├── test2.txt
└── text1.txt
aws transfer start-directory-listing
を実行すると以下のように出力先のファイル名が返却されます。
OutputFileName
の値はコネクタIDとListingId
をハイフンで結合して末尾に.json
を付与したものとなります。
% aws transfer start-directory-listing --connector-id c-xxxxx --remote-directory-path /home/sftpuser --output-directory-path /{{bucket-name}}/sftpuser-files
{
"ListingId": "c47f8544-5530-4d13-a535-496aff1822d8",
"OutputFileName": "c-xxxxx-c47f8544-5530-4d13-a535-496aff1822d8.json"
}
なお以前の検証で接続失敗する設定にしたまま上記を実行してしまったのですが特に問題なく上記同等のレスポンスが返却された上で実際にはファイルがS3に設置されていない
という形になったため組み合わせて処理を行う場合はイベント駆動とした方が良さそうです。
取得じたJSONの中身は以下のようになっておりました。あくまで対象直下のみの取得ととなるため再帰的に取得が必要な場合は別途path
から再帰的に値を取得する必要が出てきます。
{
"files" : [ {
"filePath" : "/home/sftpuser/.bash_logout",
"modifiedTimestamp" : "2020-07-15T05:58:23Z",
"size" : 18
}, {
"filePath" : "/home/sftpuser/.bash_profile",
"modifiedTimestamp" : "2020-07-15T05:58:23Z",
"size" : 193
}, {
"filePath" : "/home/sftpuser/.bashrc",
"modifiedTimestamp" : "2020-07-15T05:58:23Z",
"size" : 231
}, {
"filePath" : "/home/sftpuser/.bash_history",
"modifiedTimestamp" : "2024-04-26T06:11:39Z",
"size" : 1444
}, {
"filePath" : "/home/sftpuser/test1.txt",
"modifiedTimestamp" : "2024-02-26T03:19:15Z",
"size" : 16
}, {
"filePath" : "/home/sftpuser/test2.txt",
"modifiedTimestamp" : "2024-02-26T03:08:59Z",
"size" : 0
}, {
"filePath" : "/home/sftpuser/text1.txt",
"modifiedTimestamp" : "2024-02-26T04:09:09Z",
"size" : 1073741824
}, {
"filePath" : "/home/sftpuser/sec1.csv",
"modifiedTimestamp" : "2024-03-01T03:36:58Z",
"size" : 621
}, {
"filePath" : "/home/sftpuser/sec2.csv",
"modifiedTimestamp" : "2024-03-01T03:37:15Z",
"size" : 630
}, {
"filePath" : "/home/sftpuser/.viminfo",
"modifiedTimestamp" : "2024-03-01T03:37:15Z",
"size" : 5662
}, {
"filePath" : "/home/sftpuser/sec3.csv",
"modifiedTimestamp" : "2024-03-08T04:49:51Z",
"size" : 630
}, {
"filePath" : "/home/sftpuser/sec4.csv",
"modifiedTimestamp" : "2024-03-08T04:49:56Z",
"size" : 630
}, {
"filePath" : "/home/sftpuser/sec5.csv",
"modifiedTimestamp" : "2024-03-08T04:49:59Z",
"size" : 630
} ],
"paths" : [ {
"path" : "/home/sftpuser/.ssh"
}, {
"path" : "/home/sftpuser/sub"
} ],
"truncated" : false
}
イベント駆動による実行例
今回追加されたリスト操作の完了に対するイベントとの通知も合わせて追加されていたため、リスト操作の実行が完了するとファイルを指定したS3バケットにアップロードする仕組みを作ってみます。
大枠のイメージはこんな感じ
テンプレートおよびステートマシンの定義は以下の通りです。
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Parameters:
ConnectorId:
Type: String
Resources:
#---------------------------------------------
#---- List Complete Event Receive Machine
#---------------------------------------------
TransferExecutor:
Type: AWS::Serverless::StateMachine
Properties:
DefinitionUri: statemachine/transfer-executor.json
DefinitionSubstitutions:
ConnectorId: !Ref ConnectorId
OutputBucket: !Ref DataBucket
Role: !GetAtt TransferExecutorStateMachineRole.Arn
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}
TransferExecutorStateMachineRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: states.amazonaws.com
Policies:
- PolicyName: "transfer-execute-policy"
PolicyDocument:
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}
DataBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub sftp-connector-list-example-${AWS::AccountId}
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
statemachine/transfer-executor.json
{
"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"
}
先ほどと同じように実行すると
% aws transfer start-directory-listing --connector-id c-xxxxx --remote-directory-path /home/sftpuser --output-directory-path /sftp-connector-list-example-xxxxxx/list-result
{
"ListingId": "c0a098e6-d1a9-4551-96b4-dc4d9a75fc9c",
"OutputFileName": "c-xxxxx-c0a098e6-d1a9-4551-96b4-dc4d9a75fc9c.json"
}
転送ファイル数分StartFileTransfer
が実行され
指定したディレクトリ直下のファイルがコピーされます。
終わりに
新たしく追加された一覧機能を利用しつつファイル転送の実行を試してみました。
元々でも頑張って自前で仕組みを用意すればどうにかはできた部分ではありますが、これがAWSのAPIで実現できるようになったことで外部に別の仕組みを用意せずよりシンプルに実現できるようになったため個人的には結構大きなアップデートではないかと思います。