使用清单转移特定文件或对象

Storage Transfer Service 支持转移使用清单指定的特定文件或对象。清单是一个上传到 Cloud Storage 中的 CSV 文件,其中包含一个可供 Storage Transfer Service 操作的文件或对象列表。

清单可用于以下转移:

  • 从 AWS S3、Azure Blobstore 或 Cloud Storage 到 Cloud Storage 存储桶。

  • 从文件系统到 Cloud Storage 存储桶。

  • 从 S3 兼容存储空间到 Cloud Storage 存储桶。

  • 从 Cloud Storage 存储桶到文件系统。

  • 在两个文件系统之间。

  • 从可公开访问的 HTTP/HTTPS 来源到 Cloud Storage 存储桶。 请按照创建网址列表中的说明操作,因为网址列表的清单格式是唯一的。

创建清单

清单必须采用 CSV 格式,可以包含任何 UTF-8 字符。第一列必须是指定为字符串的文件名称或对象名称。

清单文件不支持通配符。值必须是具体的文件或对象名称。不支持没有文件或对象名称的文件夹名称。

清单文件的大小上限为 1 GiB,相当于大约 100 万行。如果您需要传输大于 1 GiB 的清单文件,可以将其拆分为多个文件并运行多个转移作业。

根据 CSV 标准,如果文件或对象名称中包含英文逗号,则必须用英文双引号将名称引起来。例如 "object1,a.txt"

我们建议您使用一小部分文件或对象测试转移作业,以避免因配置错误而导致不必要的 API 调用。

您可以从“转移作业”页面监控文件传输的状态。转移日志中会列出转移失败的文件或对象。

文件系统转移

如需在文件系统上创建文件清单,请创建只有一个列的 CSV 文件,其中包含相对于转移作业创建时指定的根目录的文件路径。

例如,您可能想要转移以下文件系统文件:

文件路径
rootdir/dir1/subdir1/file1.txt
rootdir/file2.txt
rootdir/dir2/subdir1/file3.txt

您的清单应该如以下示例所示:

dir1/subdir1/file1.txt
file2.txt
dir2/subdir1/file3.txt

对象存储转移

如需创建对象的清单,请创建一个 CSV 文件,其中第一列包含相对于转移作业创建时指定的存储桶名称和路径的对象名称。所有对象都必须位于同一存储桶中。

您还可以指定第二列(可选),其中包含要转移的特定版本的 Cloud Storage 世代编号。

例如,您可能想要转移以下对象:

对象路径 Cloud Storage 世代编号
SOURCE_PATH/object1.pdf 1664826685911832
SOURCE_PATH/object2.pdf
SOURCE_PATH/object3.pdf 1664826610699837

您的清单应该如以下示例所示:

object1.pdf,1664826685911832
object2.pdf
object3.pdf,1664826610699837

使用任意文件名和 .csv 扩展名保存清单文件。

HTTP/HTTPS 转移

如需从 HTTP 或 HTTPS 来源转移特定文件,请参阅创建网址列表中的说明。

发布清单

创建清单后,您必须将其提供给 Storage Transfer Service。Storage Transfer Service 可以访问 Cloud Storage 存储桶中的文件或文件系统中的文件。

将清单上传到 Cloud Storage

您可以将清单文件存储在任何 Cloud Storage 存储桶中。

运行转移作业的服务代理必须具有包含清单的存储桶的 storage.objects.get 权限。如需了解如何查找服务代理 ID 以及向该服务代理授予存储桶的权限,请参阅授予所需权限

如需了解如何将清单上传到存储桶,请参阅 Cloud Storage 文档中的上传对象

例如,如需使用 gcloud CLI 将文件上传到 Cloud Storage,请使用 gcloud storage cp 命令:

gcloud storage cp MANIFEST.CSV gs://DESTINATION_BUCKET_NAME/

其中:

  • MANIFEST.CSV 是清单文件的本地路径。例如 Desktop/manifest01.csv

  • DESTINATION_BUCKET_NAME 是对象要上传到的存储桶的名称。例如 my-bucket

如果成功,则响应类似如下示例:

Completed files 1/1 | 164.3kiB/164.3kiB

您可以使用客户管理的 Cloud KMS 加密密钥对清单进行加密。在这种情况下,请确保为访问清单的任何服务账号分配适用的加密密钥。不支持客户提供的密钥。

将清单存储在文件系统中

您可以将清单文件存储在来源或目标文件系统上。

文件的位置必须可供转移代理访问。如果要限制代理的目录访问权限,请确保清单文件位于装载的目录中。

开始转移

在转移操作完成之前,请勿修改清单文件。我们建议您在进行转移时锁定清单文件。

Cloud 控制台

如需通过 Cloud 控制台启动具有清单的转移作业,请执行以下操作:

  1. 按照创建转移作业中的说明选择来源、目标位置和选项。

  2. 在最后一步选择设置中,选中名为提供要通过清单文件传输的文件列表的复选框。

  3. 输入清单文件位置。

gcloud

如需转移清单中列出的文件或对象,请在 gcloud transfer jobs create 命令中添加 --manifest-file=MANIFEST_FILE 标志。

gcloud transfer jobs create SOURCE DESTINATION \
  --manifest-file=MANIFEST_FILE

MANIFEST_FILE 可以是以下任何值:

  • Cloud Storage 存储桶中 CSV 文件的路径:

    --manifest-file=gs://my_bucket/sample_manifest.csv
    

    如果存储桶或文件不是公开的,请参阅将清单上传到 Cloud Storage 以详细了解所需的权限。

  • 文件系统 SOURCE 中的相对路径,包括指定的任何路径:

    --manifest-file=source://relative_path/sample_manifest.csv
    
  • 文件系统 DESTINATION 中的相对路径,包括指定的任何路径:

    --manifest-file=destination://relative_path/sample_manifest.csv
    

REST + 客户端库

REST

如需转移清单中列出的文件或对象,请进行 createTransferJob API 调用,该调用指定添加了 transferManifest 字段的 transferSpec。例如:

POST https://storagetransfer.googleapis.com/v1/transferJobs

...
  "transferSpec": {
      "posixDataSource": {
          "rootDirectory": "/home/",
      },
      "gcsDataSink": {
          "bucketName": "GCS_NEARLINE_SINK_NAME",
          "path": "GCS_SINK_PATH",
      },
      "transferManifest": {
          "location": "gs://my_bucket/sample_manifest.csv"
      }
  }

清单文件可以存储在 Cloud Storage 存储桶中,也可以存储在来源或目标文件系统上。Cloud Storage 存储桶必须使用 gs:// 前缀并包含完整路径(包括存储桶名称)。文件系统位置必须使用 source://destination:// 前缀,并且与文件系统来源或目标位置以及可选的根目录相关。

Go


import (
	"context"
	"fmt"
	"io"

	storagetransfer "cloud.google.com/go/storagetransfer/apiv1"
	"cloud.google.com/go/storagetransfer/apiv1/storagetransferpb"
)

func transferUsingManifest(w io.Writer, projectID string, sourceAgentPoolName string, rootDirectory string, gcsSinkBucket string, manifestBucket string, manifestObjectName string) (*storagetransferpb.TransferJob, error) {
	// Your project id
	// projectId := "myproject-id"

	// The agent pool associated with the POSIX data source. If not provided, defaults to the default agent
	// sourceAgentPoolName := "projects/my-project/agentPools/transfer_service_default"

	// The root directory path on the source filesystem
	// rootDirectory := "/directory/to/transfer/source"

	// The ID of the GCS bucket to transfer data to
	// gcsSinkBucket := "my-sink-bucket"

	// The ID of the GCS bucket that contains the manifest file
	// manifestBucket := "my-manifest-bucket"

	// The name of the manifest file in manifestBucket that specifies which objects to transfer
	// manifestObjectName := "path/to/manifest.csv"

	ctx := context.Background()
	client, err := storagetransfer.NewClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("storagetransfer.NewClient: %w", err)
	}
	defer client.Close()

	manifestLocation := "gs://" + manifestBucket + "/" + manifestObjectName
	req := &storagetransferpb.CreateTransferJobRequest{
		TransferJob: &storagetransferpb.TransferJob{
			ProjectId: projectID,
			TransferSpec: &storagetransferpb.TransferSpec{
				SourceAgentPoolName: sourceAgentPoolName,
				DataSource: &storagetransferpb.TransferSpec_PosixDataSource{
					PosixDataSource: &storagetransferpb.PosixFilesystem{RootDirectory: rootDirectory},
				},
				DataSink: &storagetransferpb.TransferSpec_GcsDataSink{
					GcsDataSink: &storagetransferpb.GcsData{BucketName: gcsSinkBucket},
				},
				TransferManifest: &storagetransferpb.TransferManifest{Location: manifestLocation},
			},
			Status: storagetransferpb.TransferJob_ENABLED,
		},
	}

	resp, err := client.CreateTransferJob(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed to create transfer job: %w", err)
	}
	if _, err = client.RunTransferJob(ctx, &storagetransferpb.RunTransferJobRequest{
		ProjectId: projectID,
		JobName:   resp.Name,
	}); err != nil {
		return nil, fmt.Errorf("failed to run transfer job: %w", err)
	}
	fmt.Fprintf(w, "Created and ran transfer job from %v to %v using manifest file %v with name %v", rootDirectory, gcsSinkBucket, manifestLocation, resp.Name)
	return resp, nil
}

Java


import com.google.storagetransfer.v1.proto.StorageTransferServiceClient;
import com.google.storagetransfer.v1.proto.TransferProto;
import com.google.storagetransfer.v1.proto.TransferTypes.GcsData;
import com.google.storagetransfer.v1.proto.TransferTypes.PosixFilesystem;
import com.google.storagetransfer.v1.proto.TransferTypes.TransferJob;
import com.google.storagetransfer.v1.proto.TransferTypes.TransferManifest;
import com.google.storagetransfer.v1.proto.TransferTypes.TransferSpec;
import java.io.IOException;

public class TransferUsingManifest {

  public static void main(String[] args) throws IOException {
    // TODO(developer): Replace these variables before running the sample.

    // Your project id
    String projectId = "my-project-id";

    // The agent pool associated with the POSIX data source. If not provided, defaults to the
    // default agent
    String sourceAgentPoolName = "projects/my-project-id/agentPools/transfer_service_default";

    // The root directory path on the source filesystem
    String rootDirectory = "/directory/to/transfer/source";

    // The ID of the GCS bucket to transfer data to
    String gcsSinkBucket = "my-sink-bucket";

    // The ID of the GCS bucket which has your manifest file
    String manifestBucket = "my-bucket";

    // The ID of the object in manifestBucket that specifies which files to transfer
    String manifestObjectName = "path/to/manifest.csv";

    transferUsingManifest(
        projectId,
        sourceAgentPoolName,
        rootDirectory,
        gcsSinkBucket,
        manifestBucket,
        manifestObjectName);
  }

  public static void transferUsingManifest(
      String projectId,
      String sourceAgentPoolName,
      String rootDirectory,
      String gcsSinkBucket,
      String manifestBucket,
      String manifestObjectName)
      throws IOException {
    String manifestLocation = "gs://" + manifestBucket + "/" + manifestObjectName;
    TransferJob transferJob =
        TransferJob.newBuilder()
            .setProjectId(projectId)
            .setTransferSpec(
                TransferSpec.newBuilder()
                    .setSourceAgentPoolName(sourceAgentPoolName)
                    .setPosixDataSource(
                        PosixFilesystem.newBuilder().setRootDirectory(rootDirectory).build())
                    .setGcsDataSink((GcsData.newBuilder().setBucketName(gcsSinkBucket)).build())
                    .setTransferManifest(
                        TransferManifest.newBuilder().setLocation(manifestLocation).build()))
            .setStatus(TransferJob.Status.ENABLED)
            .build();

    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests. After completing all of your requests, call
    // the "close" method on the client to safely clean up any remaining background resources,
    // or use "try-with-close" statement to do this automatically.
    try (StorageTransferServiceClient storageTransfer = StorageTransferServiceClient.create()) {

      // Create the transfer job
      TransferJob response =
          storageTransfer.createTransferJob(
              TransferProto.CreateTransferJobRequest.newBuilder()
                  .setTransferJob(transferJob)
                  .build());

      System.out.println(
          "Created and ran a transfer job from "
              + rootDirectory
              + " to "
              + gcsSinkBucket
              + " using "
              + "manifest file "
              + manifestLocation
              + " with name "
              + response.getName());
    }
  }
}

Node.js


// Imports the Google Cloud client library
const {
  StorageTransferServiceClient,
} = require('@google-cloud/storage-transfer');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// Your project id
// const projectId = 'my-project'

// The agent pool associated with the POSIX data source. Defaults to the default agent
// const sourceAgentPoolName = 'projects/my-project/agentPools/transfer_service_default'

// The root directory path on the source filesystem
// const rootDirectory = '/directory/to/transfer/source'

// The ID of the GCS bucket to transfer data to
// const gcsSinkBucket = 'my-sink-bucket'

// Transfer manifest location. Must be a `gs:` URL
// const manifestLocation = 'gs://my-bucket/sample_manifest.csv'

// Creates a client
const client = new StorageTransferServiceClient();

/**
 * Creates a request to transfer from the local file system to the sink bucket
 */
async function transferViaManifest() {
  const createRequest = {
    transferJob: {
      projectId,
      transferSpec: {
        sourceAgentPoolName,
        posixDataSource: {
          rootDirectory,
        },
        gcsDataSink: {bucketName: gcsSinkBucket},
        transferManifest: {
          location: manifestLocation,
        },
      },
      status: 'ENABLED',
    },
  };

  // Runs the request and creates the job
  const [transferJob] = await client.createTransferJob(createRequest);

  const runRequest = {
    jobName: transferJob.name,
    projectId: projectId,
  };

  await client.runTransferJob(runRequest);

  console.log(
    `Created and ran a transfer job from '${rootDirectory}' to '${gcsSinkBucket}' using manifest \`${manifestLocation}\` with name ${transferJob.name}`
  );
}

transferViaManifest();

Python

from google.cloud import storage_transfer

def create_transfer_with_manifest(
    project_id: str,
    description: str,
    source_agent_pool_name: str,
    root_directory: str,
    sink_bucket: str,
    manifest_location: str,
):
    """Create a transfer from a POSIX file system to a GCS bucket using
    a manifest file."""

    client = storage_transfer.StorageTransferServiceClient()

    # The ID of the Google Cloud Platform Project that owns the job
    # project_id = 'my-project-id'

    # A useful description for your transfer job
    # description = 'My transfer job'

    # The agent pool associated with the POSIX data source.
    # Defaults to 'projects/{project_id}/agentPools/transfer_service_default'
    # source_agent_pool_name = 'projects/my-project/agentPools/my-agent'

    # The root directory path on the source filesystem
    # root_directory = '/directory/to/transfer/source'

    # Google Cloud Storage destination bucket name
    # sink_bucket = 'my-gcs-destination-bucket'

    # Transfer manifest location. Must be a `gs:` URL
    # manifest_location = 'gs://my-bucket/sample_manifest.csv'

    transfer_job_request = storage_transfer.CreateTransferJobRequest(
        {
            "transfer_job": {
                "project_id": project_id,
                "description": description,
                "status": storage_transfer.TransferJob.Status.ENABLED,
                "transfer_spec": {
                    "source_agent_pool_name": source_agent_pool_name,
                    "posix_data_source": {
                        "root_directory": root_directory,
                    },
                    "gcs_data_sink": {
                        "bucket_name": sink_bucket,
                    },
                    "transfer_manifest": {"location": manifest_location},
                },
            }
        }
    )

    result = client.create_transfer_job(transfer_job_request)
    print(f"Created transferJob: {result.name}")

清单中的对象或文件不一定以列出的顺序转移。

如果清单包含目标位置中已存在的文件,那么除非指定了覆盖接收器中已存在的对象选项,否则系统会跳过这些文件。

如果清单包含目标位置的其他版本中存在的对象,则目标位置中的对象会被该对象的来源版本覆盖。如果目标位置是受版本控制的存储桶,则系统会创建该对象的新版本。

后续步骤