diff --git a/mmv1/products/storageftp/Server.yaml b/mmv1/products/storageftp/Server.yaml new file mode 100644 index 000000000000..d29f04535419 --- /dev/null +++ b/mmv1/products/storageftp/Server.yaml @@ -0,0 +1,147 @@ +# Copyright 2026 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +name: 'Server' +kind: 'storageftp#server' +min_version: beta +description: | + An SFTP Server resource supporting internal and external connectivity configurations. +base_url: 'projects/{{project}}/locations/{{location}}/servers' +self_link: 'projects/{{project}}/locations/{{location}}/servers/{{server_id}}' +create_url: 'projects/{{project}}/locations/{{location}}/servers?serverId={{server_id}}' +id_format: 'projects/{{project}}/locations/{{location}}/servers/{{server_id}}' +import_format: + - 'projects/{{project}}/locations/{{location}}/servers/{{server_id}}' +update_verb: 'PATCH' +update_mask: true +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +async: + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + actions: ['create', 'delete', 'update'] +autogen_async: true +custom_code: + constants: 'templates/terraform/constants/storage_ftp_server.go.tmpl' +samples: + - name: 'storage_ftp_server_internal' + min_version: beta + primary_resource_id: 'internal_server' + steps: + - name: 'storage_ftp_server_internal' + min_version: beta + resource_id_vars: + server_id: 'sftp-int' + - name: 'storage_ftp_server_external' + min_version: beta + primary_resource_id: 'external_server' + steps: + - name: 'storage_ftp_server_external' + min_version: beta + resource_id_vars: + server_id: 'sftp-ext' +parameters: + - name: 'location' + type: String + description: | + The location (region) of the Storage FTP Server. + required: true + immutable: true + url_param_only: true + - name: 'serverId' + type: String + description: | + A unique ID for the server. Must start with a lowercase letter, and end with a lowercase letter or number. Can contain lowercase letters, numbers, and hyphens. Maximum 30 characters. + required: true + immutable: true + max_size: 30 + url_param_only: true + validation: + regex: '^[a-z0-9\-]{0,30}$' +properties: + - name: 'displayName' + type: String + description: | + A display name for the server. + required: false + - name: 'accessType' + type: Enum + description: | + The access type for this SFTP server. Possible values: INTERNAL, EXTERNAL + required: true + enum_values: + - 'INTERNAL' + - 'EXTERNAL' + - name: 'internalConfig' + type: NestedObject + description: | + Configuration for internal access type SFTP servers. + exactly_one_of: + - 'internal_config' + - 'external_config' + properties: + - name: 'consumerAcceptList' + type: Array + is_set: true + set_hash_func: 'resourceStorageFtpServerConsumerAcceptListHash' + send_empty_value: true + description: | + A list of consumer projects that are allowed to connect to this server. + item_type: + type: NestedObject + properties: + - name: 'project' + type: String + description: | + The project that is allowed to connect, in the format `projects/{project}`. + required: true + - name: 'connectionLimit' + type: Integer + description: | + The maximum number of Private Service Connect endpoints that can be created in the consumer project. + required: true + - name: 'consumerRejectList' + type: Array + is_set: true + set_hash_func: 'resourceStorageFtpServerConsumerRejectListHash' + send_empty_value: true + description: | + A list of consumer projects that are rejected from connecting to this server. + item_type: + type: NestedObject + properties: + - name: 'project' + type: String + description: | + The project that is rejected from connecting, in the format `projects/{project}`. + required: true + - name: 'externalConfig' + type: NestedObject + description: | + Configuration for external access type SFTP servers. + exactly_one_of: + - 'internal_config' + - 'external_config' + properties: + - name: 'allowedCidrBlocks' + type: Array + is_set: true + min_size: 1 + description: | + A list of allowed IPv4 or IPv6 CIDR block ranges that can connect to this server. + item_type: + type: String diff --git a/mmv1/products/storageftp/product.yaml b/mmv1/products/storageftp/product.yaml new file mode 100644 index 000000000000..19be244a6842 --- /dev/null +++ b/mmv1/products/storageftp/product.yaml @@ -0,0 +1,23 @@ +# Copyright 2026 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +name: StorageFtp +display_name: Cloud Storage FTP +scopes: + - https://www.googleapis.com/auth/cloud-platform +versions: + - name: beta + base_url: https://ftp.googleapis.com/v1alpha/ + - name: ga + base_url: https://ftp.googleapis.com/v1alpha/ diff --git a/mmv1/templates/terraform/constants/storage_ftp_server.go.tmpl b/mmv1/templates/terraform/constants/storage_ftp_server.go.tmpl new file mode 100644 index 000000000000..48c230fc7760 --- /dev/null +++ b/mmv1/templates/terraform/constants/storage_ftp_server.go.tmpl @@ -0,0 +1,38 @@ +{{/* + The license inside this block applies to this file + Copyright 2026 Google Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ -}} + +func resourceStorageFtpServerConsumerAcceptListHash(v interface{}) int { + if v == nil { + return 0 + } + + m, ok := v.(map[string]interface{}) + if !ok { + return 0 + } + + return tpgresource.Hashcode(fmt.Sprintf("%v-%v", m["project"], m["connection_limit"])) +} + +func resourceStorageFtpServerConsumerRejectListHash(v interface{}) int { + if v == nil { + return 0 + } + + m, ok := v.(map[string]interface{}) + if !ok { + return 0 + } + + return tpgresource.Hashcode(fmt.Sprintf("%v", m["project"])) +} diff --git a/mmv1/templates/terraform/samples/services/storageftp/storage_ftp_server_external.tf.tmpl b/mmv1/templates/terraform/samples/services/storageftp/storage_ftp_server_external.tf.tmpl new file mode 100644 index 000000000000..a2f55390c012 --- /dev/null +++ b/mmv1/templates/terraform/samples/services/storageftp/storage_ftp_server_external.tf.tmpl @@ -0,0 +1,14 @@ +resource "google_storage_ftp_server" "{{$.PrimaryResourceId}}" { + provider = google-beta + location = "us-west1" + server_id = "{{index $.ResourceIdVars "server_id"}}" + display_name = "My SFTP Server" + access_type = "EXTERNAL" + + external_config { + allowed_cidr_blocks = [ + "192.168.1.0/24", + "10.0.0.0/8" + ] + } +} diff --git a/mmv1/templates/terraform/samples/services/storageftp/storage_ftp_server_internal.tf.tmpl b/mmv1/templates/terraform/samples/services/storageftp/storage_ftp_server_internal.tf.tmpl new file mode 100644 index 000000000000..8ede4aa8ef6c --- /dev/null +++ b/mmv1/templates/terraform/samples/services/storageftp/storage_ftp_server_internal.tf.tmpl @@ -0,0 +1,24 @@ +resource "google_storage_ftp_server" "{{$.PrimaryResourceId}}" { + provider = google-beta + location = "us-west1" + server_id = "{{index $.ResourceIdVars "server_id"}}" + display_name = "My SFTP Server" + access_type = "INTERNAL" + + internal_config { + consumer_accept_list { + project = "projects/${data.google_project.project.project_id}" + connection_limit = 10 + } + consumer_reject_list { + project = "projects/${data.google_project.project.number}" + } + consumer_reject_list { + project = "projects/920946329098" + } + } +} + +data "google_project" "project" { + provider = google-beta +} diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt index 9fa00e9622db..ebfeff095950 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt @@ -881,6 +881,11 @@ var ServicesListBeta = mapOf( "displayName" to "Storagecontrol", "path" to "./google-beta/services/storagecontrol" ), + "storageftp" to mapOf( + "name" to "storageftp", + "displayName" to "Storageftp", + "path" to "./google-beta/services/storageftp" + ), "storageinsights" to mapOf( "name" to "storageinsights", "displayName" to "Storageinsights", diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt index 50d1b659f644..9b5858df3100 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt @@ -876,6 +876,11 @@ var ServicesListGa = mapOf( "displayName" to "Storagecontrol", "path" to "./google/services/storagecontrol" ), + "storageftp" to mapOf( + "name" to "storageftp", + "displayName" to "Storageftp", + "path" to "./google/services/storageftp" + ), "storageinsights" to mapOf( "name" to "storageinsights", "displayName" to "Storageinsights", diff --git a/mmv1/third_party/terraform/services/storageftp/resource_storage_ftp_server_test.go.tmpl b/mmv1/third_party/terraform/services/storageftp/resource_storage_ftp_server_test.go.tmpl new file mode 100644 index 000000000000..0da2e9545534 --- /dev/null +++ b/mmv1/third_party/terraform/services/storageftp/resource_storage_ftp_server_test.go.tmpl @@ -0,0 +1,220 @@ +package storageftp_test + +{{- if ne $.TargetVersionName "ga" }} + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-provider-google/google/acctest" + _ "github.com/hashicorp/terraform-provider-google/google/services/resourcemanager" + _ "github.com/hashicorp/terraform-provider-google/google/services/storageftp" +) + +func TestAccStorageFtpServer_updateInternal(t *testing.T) { + t.Parallel() + + serverId1 := fmt.Sprintf("tf-sftp-int-%s", acctest.RandString(t, 10)) + serverId2 := fmt.Sprintf("tf-sftp-int2-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckStorageFtpServerDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageFtpServer_internalInitial(serverId1), + }, + { + ResourceName: "google_storage_ftp_server.internal_server", + ImportState: true, + ImportStateVerify: true, + // Ignore location and server_id as they are URL-only parameters. + ImportStateVerifyIgnore: []string{"location", "server_id"}, + }, + { + Config: testAccStorageFtpServer_internalUpdated(serverId1), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_storage_ftp_server.internal_server", plancheck.ResourceActionUpdate), + }, + }, + }, + { + ResourceName: "google_storage_ftp_server.internal_server", + ImportState: true, + ImportStateVerify: true, + // Ignore location and server_id as they are URL-only parameters. + ImportStateVerifyIgnore: []string{"location", "server_id"}, + }, + { + Config: testAccStorageFtpServer_internalUpdated(serverId2), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_storage_ftp_server.internal_server", plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + }, + { + ResourceName: "google_storage_ftp_server.internal_server", + ImportState: true, + ImportStateVerify: true, + // Ignore location and server_id as they are URL-only parameters. + ImportStateVerifyIgnore: []string{"location", "server_id"}, + }, + }, + }) +} + +func TestAccStorageFtpServer_updateExternal(t *testing.T) { + t.Parallel() + + serverId1 := fmt.Sprintf("tf-sftp-ext-%s", acctest.RandString(t, 10)) + serverId2 := fmt.Sprintf("tf-sftp-ext2-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckStorageFtpServerDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageFtpServer_externalInitial(serverId1), + }, + { + ResourceName: "google_storage_ftp_server.external_server", + ImportState: true, + ImportStateVerify: true, + // Ignore location and server_id as they are URL-only parameters. + ImportStateVerifyIgnore: []string{"location", "server_id"}, + }, + { + Config: testAccStorageFtpServer_externalUpdated(serverId1), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_storage_ftp_server.external_server", plancheck.ResourceActionUpdate), + }, + }, + }, + { + ResourceName: "google_storage_ftp_server.external_server", + ImportState: true, + ImportStateVerify: true, + // Ignore location and server_id as they are URL-only parameters. + ImportStateVerifyIgnore: []string{"location", "server_id"}, + }, + { + Config: testAccStorageFtpServer_externalUpdated(serverId2), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_storage_ftp_server.external_server", plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + }, + { + ResourceName: "google_storage_ftp_server.external_server", + ImportState: true, + ImportStateVerify: true, + // Ignore location and server_id as they are URL-only parameters. + ImportStateVerifyIgnore: []string{"location", "server_id"}, + }, + }, + }) +} + +func testAccStorageFtpServer_internalInitial(serverId string) string { + return fmt.Sprintf(` +resource "google_storage_ftp_server" "internal_server" { + provider = google-beta + location = "us-west1" + server_id = "%s" + display_name = "Initial Internal SFTP Server" + access_type = "INTERNAL" + + internal_config { + consumer_accept_list { + project = "projects/${data.google_project.project.project_id}" + connection_limit = 10 + } + consumer_reject_list { + project = "projects/${data.google_project.project.number}" + } + } +} + +data "google_project" "project" { + provider = google-beta +} +`, serverId) +} + +func testAccStorageFtpServer_internalUpdated(serverId string) string { + return fmt.Sprintf(` +resource "google_storage_ftp_server" "internal_server" { + provider = google-beta + location = "us-west1" + server_id = "%s" + display_name = "Updated Internal SFTP Server" + access_type = "INTERNAL" + + internal_config { + consumer_accept_list { + project = "projects/${data.google_project.project.project_id}" + connection_limit = 25 + } + consumer_reject_list { + project = "projects/${data.google_project.project.number}" + } + consumer_reject_list { + project = "projects/920946329098" + } + } +} + +data "google_project" "project" { + provider = google-beta +} +`, serverId) +} + +func testAccStorageFtpServer_externalInitial(serverId string) string { + return fmt.Sprintf(` +resource "google_storage_ftp_server" "external_server" { + provider = google-beta + location = "us-west1" + server_id = "%s" + display_name = "Initial External SFTP Server" + access_type = "EXTERNAL" + + external_config { + allowed_cidr_blocks = [ + "192.168.1.0/24", + "10.0.0.0/8", + ] + } +} +`, serverId) +} + +func testAccStorageFtpServer_externalUpdated(serverId string) string { + return fmt.Sprintf(` +resource "google_storage_ftp_server" "external_server" { + provider = google-beta + location = "us-west1" + server_id = "%s" + display_name = "Updated External SFTP Server" + access_type = "EXTERNAL" + + external_config { + allowed_cidr_blocks = [ + "172.16.0.0/12", + "10.0.0.0/16", + "192.168.0.0/16", + ] + } +} +`, serverId) +} + +{{- end }}