[アップデート] AWS Transfer Family SFTP コネクタがSFTPサーバ上のファイル削除・移動操作をサポートしました

[アップデート] AWS Transfer Family SFTP コネクタがSFTPサーバ上のファイル削除・移動操作をサポートしました

Clock Icon2025.04.10

初めに

先日SFTP コネクタでSFTPサーバ上のファイルの削除・移動(名前変更を含む)操作の機能追加のアップデートがありました。

https://aws.amazon.com/about-aws/whats-new/2025/04/aws-transfer-family-sftp-connectors-delete-rename-move-files-remote-sftp-servers/

SFTP コネクタはSFTPサーバとS3の間のファイル転送を行えるサービスで、本アップデート以前ではS3とSFTPサーバ間のファイルの転送(GET&PUT)及びSFTPサーバ側のファイルの一覧取得操作(LIST)をサポートしていました。

https://dev.classmethod.jp/articles/aws-transfer-family-release-sftp-connector/

https://dev.classmethod.jp/articles/sftp-connector-support-listing-file-on-sftp-server/

今回のアップデートではこれに加えてSFTPサーバ上のファイルの削除及び移動がサポートされるようになったため、
ファイル転送後は送信元にファイルを残す必要がない場合はその削除、処理済みファイル置き場のような場所に移動といった後処理までAWS APIで完結可能となります。

実際に使ってみる

SFTPサーバ上のcurディレクトリの中身をS3バケットに転送し、転送が完了したファイルはcurからarcディレクトリに移動という操作をAWSサービス上で完結させてみます。

少しごちゃっとしてますが構成イメージは以下の通りです。起動はCLIでstart-directory-listingを実行する形で発火させます。

sftp-connector-mv-overview

ファイル移動が本趣旨ですが、ファイル取得・設置・一覧取得、イベント駆動とここまでリリースされてきた機能ほぼ全盛です。

本当はLambda関数使わずにもStep Functionsで済ませたかったのですが、本記事執筆時点(2025/04/10)でまだStep Functions側のアクション一覧になかったのでLambda関数で処理を呼び出してます。Step Functions側が対応すればあえてLambda関数を使う必要はないです。

※ 直打ちしたけど当然エラー
sfn-start-remote-move

実装

今回はAWS SAMで実装しています。

SAMテンプレート

SFTPコネクタは含まれないので別途セットアップしてください。

template.yml
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}

ファイル転送用ステートマシン定義

ファイル一覧機能実装時の記事記載のものをそのまま流用しています。

transfer-executer.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"
  }

ファイル移動処理用Lambda関数

functions/move_file/app.py
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を記載して入れ込んでます。

requirements.txt
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上にファイルが設置されます。
transfer-list-and-cp-sfn

transfer-list-and-cp-s3-result

転送完了するとファイル単位でLambda関数が動き出しファイルが移動されます。

$ tree cur arc
cur
arc
├── sec1.csv
├── sec2.csv
├── sec3.csv
├── sec4.csv
└── sec5.csv

移動時のSFTPコネクタのログは以下のような感じです。ファイルサイズが同じくらいな関係でRETRIEVEが全部出た後MOVEという形にはなってますがイベント自体はファイル単位で行われてるはずなので実運用だともう少しごちゃごちゃする形になると思います。

sftp-connector-move-log

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のサンプルイベントを見たら移動完了/失敗イベントがあったのでドキュメントの更新が追いついてないだけでイベントはありそうです。なのでやはり非同期処理でしょうか。

event-bridge-sftp-move-complete

https://docs.aws.amazon.com/transfer/latest/userguide/events-detail-reference.html#event-detail-sftp-connector-events

実行(削除処理)

ついでに削除処理も見ておきましょう。

先ほどのLambda関数の内容を変更しcurからarcに移動ではなくファイル削除にします。

app.py
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レイヤーから対応できるのは結構嬉しいのではないでしょうか。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.