Skip to content

Commit 31f253e

Browse files
Added support for TransferManifest option in TransferSpec for google_storage_transfer_job (#15367)
1 parent 83623a9 commit 31f253e

File tree

4 files changed

+267
-0
lines changed

4 files changed

+267
-0
lines changed

mmv1/third_party/terraform/services/storagetransfer/resource_storage_transfer_job.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,13 @@ func ResourceStorageTransferJob() *schema.Resource {
302302
ExactlyOneOf: transferSpecDataSourceKeys,
303303
Description: `An AWS S3 Compatible data source.`,
304304
},
305+
"transfer_manifest": {
306+
Type: schema.TypeList,
307+
Optional: true,
308+
MaxItems: 1,
309+
Elem: transferManifest(),
310+
Description: `A manifest file listing specific objects to transfer.`,
311+
},
305312
},
306313
},
307314
Description: `Transfer specification.`,
@@ -827,6 +834,19 @@ func hdfsDataSchema() *schema.Resource {
827834
}
828835
}
829836

837+
func transferManifest() *schema.Resource {
838+
return &schema.Resource{
839+
Schema: map[string]*schema.Schema{
840+
"location": {
841+
Type: schema.TypeString,
842+
Required: true,
843+
Description: `Cloud Storage path to the manifest CSV.`,
844+
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^gs://[^/]+/.+`), "must be a Cloud path like gs://BUCKET/path/manifest.csv"),
845+
},
846+
},
847+
}
848+
}
849+
830850
func azureBlobStorageDataSchema() *schema.Resource {
831851
return &schema.Resource{
832852
Schema: map[string]*schema.Schema{
@@ -1617,6 +1637,26 @@ func flattenHdfsData(hdfsData *storagetransfer.HdfsData) []map[string]interface{
16171637
return []map[string]interface{}{data}
16181638
}
16191639

1640+
func expandTransferManifest(manifest []interface{}) *storagetransfer.TransferManifest {
1641+
if len(manifest) == 0 || manifest[0] == nil {
1642+
return nil
1643+
}
1644+
1645+
manifestFile := manifest[0].(map[string]interface{})
1646+
return &storagetransfer.TransferManifest{
1647+
Location: manifestFile["location"].(string),
1648+
}
1649+
}
1650+
1651+
func flattenTransferManifest(manifest *storagetransfer.TransferManifest) []map[string]interface{} {
1652+
1653+
return []map[string]interface{}{
1654+
{
1655+
"location": manifest.Location,
1656+
},
1657+
}
1658+
}
1659+
16201660
func expandAwsS3CompatibleData(awsS3CompatibleDataSchema []interface{}) *storagetransfer.AwsS3CompatibleData {
16211661
if len(awsS3CompatibleDataSchema) == 0 || awsS3CompatibleDataSchema[0] == nil {
16221662
return nil
@@ -1849,6 +1889,7 @@ func expandTransferSpecs(transferSpecs []interface{}) *storagetransfer.TransferS
18491889
PosixDataSource: expandPosixData(transferSpec["posix_data_source"].([]interface{})),
18501890
HdfsDataSource: expandHdfsData(transferSpec["hdfs_data_source"].([]interface{})),
18511891
AwsS3CompatibleDataSource: expandAwsS3CompatibleData(transferSpec["aws_s3_compatible_data_source"].([]interface{})),
1892+
TransferManifest: expandTransferManifest(transferSpec["transfer_manifest"].([]interface{})),
18521893
}
18531894
}
18541895

@@ -1892,6 +1933,9 @@ func flattenTransferSpec(transferSpec *storagetransfer.TransferSpec, d *schema.R
18921933
} else if transferSpec.AwsS3CompatibleDataSource != nil {
18931934
data["aws_s3_compatible_data_source"] = flattenAwsS3CompatibleData(transferSpec.AwsS3CompatibleDataSource, d)
18941935
}
1936+
if transferSpec.TransferManifest != nil && transferSpec.TransferManifest.Location != "" {
1937+
data["transfer_manifest"] = flattenTransferManifest(transferSpec.TransferManifest)
1938+
}
18951939

18961940
return []map[string]interface{}{data}
18971941
}

mmv1/third_party/terraform/services/storagetransfer/resource_storage_transfer_job_meta.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ fields:
8181
- field: 'transfer_spec.posix_data_source.root_directory'
8282
- field: 'transfer_spec.sink_agent_pool_name'
8383
- field: 'transfer_spec.source_agent_pool_name'
84+
- field: 'transfer_spec.transfer_manifest.location'
8485
- field: 'transfer_spec.transfer_options.delete_objects_from_source_after_transfer'
8586
- field: 'transfer_spec.transfer_options.delete_objects_unique_in_sink'
8687
- field: 'transfer_spec.transfer_options.overwrite_objects_already_existing_in_sink'

mmv1/third_party/terraform/services/storagetransfer/resource_storage_transfer_job_test.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,70 @@ func TestAccStorageTransferJob_awsS3CompatibleDataSource(t *testing.T) {
664664
})
665665
}
666666

667+
func TestAccStorageTransferJob_transferManifest(t *testing.T) {
668+
t.Parallel()
669+
670+
project := envvar.GetTestProjectFromEnv()
671+
src := acctest.RandString(t, 10)
672+
dst := acctest.RandString(t, 10)
673+
desc := acctest.RandString(t, 10)
674+
675+
manifestV1 := fmt.Sprintf("manifest-%s-v1.csv", acctest.RandString(t, 6))
676+
manifestV2 := fmt.Sprintf("manifest-%s-v2.csv", acctest.RandString(t, 6))
677+
678+
acctest.VcrTest(t, resource.TestCase{
679+
PreCheck: func() { acctest.AccTestPreCheck(t) },
680+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
681+
CheckDestroy: testAccStorageTransferJobDestroyProducer(t),
682+
Steps: []resource.TestStep{
683+
{
684+
Config: testAccStorageTransferJob_withTransferManifest(project, src, dst, desc, manifestV1),
685+
Check: resource.ComposeAggregateTestCheckFunc(
686+
resource.TestCheckResourceAttr(
687+
"google_storage_transfer_job.transfer_job",
688+
"transfer_spec.0.transfer_manifest.0.location",
689+
fmt.Sprintf("gs://%s/%s", src, manifestV1),
690+
),
691+
),
692+
},
693+
{
694+
ResourceName: "google_storage_transfer_job.transfer_job",
695+
ImportState: true,
696+
ImportStateVerify: true,
697+
},
698+
{
699+
Config: testAccStorageTransferJob_withTransferManifest(project, src, dst, desc, manifestV2),
700+
Check: resource.ComposeAggregateTestCheckFunc(
701+
resource.TestCheckResourceAttr(
702+
"google_storage_transfer_job.transfer_job",
703+
"transfer_spec.0.transfer_manifest.0.location",
704+
fmt.Sprintf("gs://%s/%s", src, manifestV2),
705+
),
706+
),
707+
},
708+
{
709+
ResourceName: "google_storage_transfer_job.transfer_job",
710+
ImportState: true,
711+
ImportStateVerify: true,
712+
},
713+
{
714+
Config: testAccStorageTransferJob_withoutTransferManifest(project, src, dst, desc),
715+
Check: resource.ComposeAggregateTestCheckFunc(
716+
resource.TestCheckNoResourceAttr(
717+
"google_storage_transfer_job.transfer_job",
718+
"transfer_spec.0.transfer_manifest.0.location",
719+
),
720+
),
721+
},
722+
{
723+
ResourceName: "google_storage_transfer_job.transfer_job",
724+
ImportState: true,
725+
ImportStateVerify: true,
726+
},
727+
},
728+
})
729+
}
730+
667731
func testAccStorageTransferJobDestroyProducer(t *testing.T) func(s *terraform.State) error {
668732
return func(s *terraform.State) error {
669733
config := acctest.GoogleProviderConfig(t)
@@ -2886,3 +2950,155 @@ resource "google_storage_transfer_job" "with_sa" {
28862950
}
28872951
`, project, dataSourceBucketName, project, dataSinkBucketName, project, description, project)
28882952
}
2953+
2954+
func testAccStorageTransferJob_withTransferManifest(project, dataSourceBucketName, dataSinkBucketName, transferJobDescription, manifestObjectName string) string {
2955+
return fmt.Sprintf(`
2956+
data "google_storage_transfer_project_service_account" "default" {
2957+
project = "%[1]s"
2958+
}
2959+
2960+
resource "google_storage_bucket" "data_source" {
2961+
project = "%[1]s"
2962+
name = "%[2]s"
2963+
location = "US"
2964+
force_destroy = true
2965+
uniform_bucket_level_access = true
2966+
}
2967+
2968+
resource "google_storage_bucket" "data_sink" {
2969+
project = "%[1]s"
2970+
name = "%[3]s"
2971+
location = "US"
2972+
force_destroy = true
2973+
uniform_bucket_level_access = true
2974+
}
2975+
2976+
resource "google_storage_bucket_iam_member" "source_iam" {
2977+
bucket = google_storage_bucket.data_source.name
2978+
role = "roles/storage.admin"
2979+
member = "serviceAccount:${data.google_storage_transfer_project_service_account.default.email}"
2980+
}
2981+
2982+
resource "google_storage_bucket_iam_member" "sink_iam" {
2983+
bucket = google_storage_bucket.data_sink.name
2984+
role = "roles/storage.admin"
2985+
member = "serviceAccount:${data.google_storage_transfer_project_service_account.default.email}"
2986+
}
2987+
2988+
resource "google_storage_bucket_object" "manifest"{
2989+
name = "%[5]s"
2990+
bucket = google_storage_bucket.data_source.name
2991+
content = "dummy-manifest-content"
2992+
depends_on = [
2993+
google_storage_bucket_iam_member.sink_iam, google_storage_bucket_iam_member.source_iam,
2994+
]
2995+
}
2996+
2997+
resource "google_storage_transfer_job" "transfer_job" {
2998+
description = "%[4]s"
2999+
project = "%[1]s"
3000+
3001+
transfer_spec {
3002+
transfer_manifest {
3003+
location = "gs://${google_storage_bucket.data_source.name}/${google_storage_bucket_object.manifest.name}"
3004+
}
3005+
gcs_data_source {
3006+
bucket_name = google_storage_bucket.data_source.name
3007+
path = "foo/bar/"
3008+
}
3009+
gcs_data_sink {
3010+
bucket_name = google_storage_bucket.data_sink.name
3011+
path = "foo/bar/"
3012+
}
3013+
}
3014+
3015+
schedule {
3016+
schedule_start_date {
3017+
year = 2023
3018+
month = 1
3019+
day = 13
3020+
}
3021+
schedule_end_date {
3022+
year = 2023
3023+
month = 1
3024+
day = 13
3025+
}
3026+
}
3027+
3028+
depends_on = [
3029+
google_storage_bucket_object.manifest,
3030+
]
3031+
3032+
}
3033+
`, project, dataSourceBucketName, dataSinkBucketName, transferJobDescription, manifestObjectName)
3034+
}
3035+
3036+
func testAccStorageTransferJob_withoutTransferManifest(project, dataSourceBucketName, dataSinkBucketName, transferJobDescription string) string {
3037+
return fmt.Sprintf(`
3038+
data "google_storage_transfer_project_service_account" "default" {
3039+
project = "%[1]s"
3040+
}
3041+
3042+
resource "google_storage_bucket" "data_source" {
3043+
project = "%[1]s"
3044+
name = "%[2]s"
3045+
location = "US"
3046+
force_destroy = true
3047+
uniform_bucket_level_access = true
3048+
}
3049+
3050+
resource "google_storage_bucket" "data_sink" {
3051+
project = "%[1]s"
3052+
name = "%[3]s"
3053+
location = "US"
3054+
force_destroy = true
3055+
uniform_bucket_level_access = true
3056+
}
3057+
3058+
resource "google_storage_bucket_iam_member" "source_iam" {
3059+
bucket = google_storage_bucket.data_source.name
3060+
role = "roles/storage.admin"
3061+
member = "serviceAccount:${data.google_storage_transfer_project_service_account.default.email}"
3062+
}
3063+
3064+
resource "google_storage_bucket_iam_member" "sink_iam" {
3065+
bucket = google_storage_bucket.data_sink.name
3066+
role = "roles/storage.admin"
3067+
member = "serviceAccount:${data.google_storage_transfer_project_service_account.default.email}"
3068+
}
3069+
3070+
resource "google_storage_transfer_job" "transfer_job" {
3071+
description = "%[4]s"
3072+
project = "%[1]s"
3073+
3074+
transfer_spec {
3075+
gcs_data_source {
3076+
bucket_name = google_storage_bucket.data_source.name
3077+
path = "foo/bar/"
3078+
}
3079+
gcs_data_sink {
3080+
bucket_name = google_storage_bucket.data_sink.name
3081+
path = "foo/bar/"
3082+
}
3083+
}
3084+
3085+
schedule {
3086+
schedule_start_date {
3087+
year = 2023
3088+
month = 1
3089+
day = 13
3090+
}
3091+
schedule_end_date {
3092+
year = 2023
3093+
month = 1
3094+
day = 13
3095+
}
3096+
}
3097+
3098+
depends_on = [
3099+
google_storage_bucket_iam_member.sink_iam, google_storage_bucket_iam_member.source_iam,
3100+
]
3101+
3102+
}
3103+
`, project, dataSourceBucketName, dataSinkBucketName, transferJobDescription)
3104+
}

mmv1/third_party/terraform/website/docs/r/storage_transfer_job.html.markdown

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ The following arguments are supported:
176176

177177
* `aws_s3_compatible_data_source` - (Optional) An AWS S3 Compatible data source. Structure [documented below](#nested_aws_s3_compatible_data_source).
178178

179+
* `transfer_manifest` - (Optional) Use a manifest file to limit which object are transferred. See [Storage Transfer Service manifest file format](https://cloud.google.com/storage-transfer/docs/manifest). Structure [documented below](#nested_transfer_manifest).
180+
179181
<a name="nested_replication_spec"></a>The `replication_spec` block supports:
180182

181183
* `gcs_data_sink` - (Optional) A Google Cloud Storage data sink. Structure [documented below](#nested_gcs_data_sink).
@@ -293,6 +295,10 @@ A duration in seconds with up to nine fractional digits, terminated by 's'. Exam
293295

294296
* `credentials_secret` - (Optional) The Resource name of a secret in Secret Manager. AWS credentials must be stored in Secret Manager in JSON format. If credentials_secret is specified, do not specify role_arn or aws_access_key. Format: `projects/{projectNumber}/secrets/{secret_name}`.
295297

298+
<a name="nested_transfer_manifest"></a>The `transfer_manifest` block supports:
299+
300+
* `location` - (Required) The **GCS URI** to the manifest file (CSV or line-delimited). Example: `gs://my-bucket/manifest.csv`
301+
296302
The `aws_access_key` block supports:
297303

298304
* `access_key_id` - (Required) AWS Key ID.

0 commit comments

Comments
 (0)