[アップデート]SFTPコネクタにSFTPサーバ上のファイル一覧を取得する機能が追加されました

2024.04.30

初めに

先日Transfer FamilyのSFTPコネクタにリモートサーバからファイル一覧を取得する機能が追加されました。

従来ではSFTPコネクタはあくまでS3とSFTPサーバ間のファイルの転送のみをサポートしており、そのファイルの転送の指定にもワイルドーカードや正規表現の利用もできず完全一致を行うようにファイル名を指定する必要があった為、ファイル名が動的な値の場合は何かしら別の手段で取得する必要がありました。

転送元がS3の場合はListObjectsで取得できるためStep Functionsと組み合わせシンプルに実装ができるものの、転送元がSFTPサーバとなるとOS上のレイヤーなこともありAWSレイヤーからどうにかしようとする場合はシンプルに実装は難しいものとなっておりました。

今回のアップデートで追加された機能により転送元がSFTPサーバであってもシンプルにその一覧が取得できるようになったため試してみます。

実際に取得してみる

StartDirectoryListingAPI、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で実現できるようになったことで外部に別の仕組みを用意せずよりシンプルに実現できるようになったため個人的には結構大きなアップデートではないかと思います。