Skip to content

Commit

Permalink
Add buildServiceAccount field to cloudfunctions (#11100)
Browse files Browse the repository at this point in the history
  • Loading branch information
nifflets authored Jul 10, 2024
1 parent 07fdf0f commit a1b89f3
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 1 deletion.
4 changes: 4 additions & 0 deletions mmv1/products/cloudfunctions/CloudFunction.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ properties:
name: 'serviceAccountEmail'
output: true
description: 'The email of the service account for this function.'
- !ruby/object:Api::Type::String
name: 'buildServiceAccount'
default_from_api: true
description: 'The fully-qualified name of the service account to be used for the build step of deploying this function'
- !ruby/object:Api::Type::String
name: 'updateTime'
output: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ func ResourceCloudFunctionsFunction() *schema.Resource {
Description: ` If provided, the self-provided service account to run the function with.`,
},

"build_service_account": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: `The fully-qualified name of the service account to be used for the build step of deploying this function`,
},

"vpc_connector": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -627,6 +634,10 @@ func resourceCloudFunctionsCreate(d *schema.ResourceData, meta interface{}) erro
function.MinInstances = int64(v.(int))
}

if v, ok := d.GetOk("build_service_account"); ok {
function.BuildServiceAccount = v.(string)
}

log.Printf("[DEBUG] Creating cloud function: %s", function.Name)

// We retry the whole create-and-wait because Cloud Functions
Expand Down Expand Up @@ -714,6 +725,9 @@ func resourceCloudFunctionsRead(d *schema.ResourceData, meta interface{}) error
if err := d.Set("service_account_email", function.ServiceAccountEmail); err != nil {
return fmt.Errorf("Error setting service_account_email: %s", err)
}
if err := d.Set("build_service_account", function.BuildServiceAccount); err != nil {
return fmt.Errorf("Error setting build_service_account: %s", err)
}
if err := d.Set("environment_variables", function.EnvironmentVariables); err != nil {
return fmt.Errorf("Error setting environment_variables: %s", err)
}
Expand Down Expand Up @@ -945,6 +959,11 @@ func resourceCloudFunctionsUpdate(d *schema.ResourceData, meta interface{}) erro
updateMaskArr = append(updateMaskArr, "minInstances")
}

if d.HasChange("build_service_account") {
function.BuildServiceAccount = d.Get("build_service_account").(string)
updateMaskArr = append(updateMaskArr, "buildServiceAccount")
}

if len(updateMaskArr) > 0 {
log.Printf("[DEBUG] Send Patch CloudFunction Configuration request: %#v", function)
updateMask := strings.Join(updateMaskArr, ",")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,54 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) {
})
}

func TestAccCloudFunctionsFunction_buildServiceAccount(t *testing.T) {
t.Parallel()

funcResourceName := "google_cloudfunctions_function.function"
functionName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
bucketName := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt(t))
zipFilePath := acctest.CreateZIPArchiveForCloudFunctionSource(t, testHTTPTriggerPath)
proj := envvar.GetTestProjectFromEnv()
serviceAccount1 := fmt.Sprintf("tf-test-build1-%s", acctest.RandString(t, 10))
serviceAccount2 := fmt.Sprintf("tf-test-build2-%s", acctest.RandString(t, 10))
defer os.Remove(zipFilePath) // clean up

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
ExternalProviders: map[string]resource.ExternalProvider{
"time": {},
},
CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccCloudFunctionsFunction_buildServiceAccount("sa1", functionName, bucketName, zipFilePath, serviceAccount1),
Check: resource.TestCheckResourceAttr(funcResourceName,
"build_service_account",
fmt.Sprintf("projects/%[1]s/serviceAccounts/%s@%[1]s.iam.gserviceaccount.com", proj, serviceAccount1)),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"build_environment_variables"},
},
{
Config: testAccCloudFunctionsFunction_buildServiceAccount("sa2", functionName, bucketName, zipFilePath, serviceAccount2),
Check: resource.TestCheckResourceAttr(funcResourceName,
"build_service_account",
fmt.Sprintf("projects/%[1]s/serviceAccounts/%s@%[1]s.iam.gserviceaccount.com", proj, serviceAccount2)),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"build_environment_variables"},
},
},
})
}

func testAccCheckCloudFunctionsFunctionDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
config := acctest.GoogleProviderConfig(t)
Expand Down Expand Up @@ -1292,3 +1340,67 @@ resource "google_cloudfunctions_function" "function" {
}
`, accountId, secretName, versionName, bucketName, zipFilePath, functionName, projectNumber, versionNumber)
}

func testAccCloudFunctionsFunction_buildServiceAccount(saName, functionName, bucketName, zipFilePath, serviceAccount string) string {
return fmt.Sprintf(`
data "google_project" "project" {}

resource "google_storage_bucket" "bucket" {
name = "%s"
location = "US"
uniform_bucket_level_access = true
}

resource "google_storage_bucket_object" "archive" {
name = "index.zip"
bucket = google_storage_bucket.bucket.name
source = "%s"
}

resource "google_service_account" "cloud_function_build_account_%[3]s" {
account_id = "%s"
display_name = "Testing Cloud Function build service account"
}

resource "google_project_iam_member" "storage_object_admin_%[3]s" {
project = data.google_project.project.project_id
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.cloud_function_build_account_%[3]s.email}"
}

resource "google_project_iam_member" "artifact_registry_writer_%[3]s" {
project = data.google_project.project.project_id
role = "roles/artifactregistry.writer"
member = "serviceAccount:${google_service_account.cloud_function_build_account_%[3]s.email}"
}

resource "google_project_iam_member" "log_writer_%[3]s" {
project = data.google_project.project.project_id
role = "roles/logging.logWriter"
member = "serviceAccount:${google_service_account.cloud_function_build_account_%[3]s.email}"
}

resource "time_sleep" "wait_iam_roles_%[3]s" {
depends_on = [
google_project_iam_member.storage_object_admin_%[3]s,
google_project_iam_member.artifact_registry_writer_%[3]s,
google_project_iam_member.log_writer_%[3]s,
]
create_duration = "60s"
}

resource "google_cloudfunctions_function" "function" {
depends_on = [time_sleep.wait_iam_roles_%[3]s]
name = "%[5]s"
runtime = "nodejs10"

source_archive_bucket = google_storage_bucket.bucket.name
source_archive_object = google_storage_bucket_object.archive.name

build_service_account = google_service_account.cloud_function_build_account_%[3]s.name

trigger_http = true
entry_point = "helloGET"
}
`, bucketName, zipFilePath, saName, serviceAccount, functionName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ Please refer to the field 'effective_labels' for all of the labels present on th

* `service_account_email` - (Optional) If provided, the self-provided service account to run the function with.

* `build_service_account` - (Optional) If provided, the self-provided service account to use to build the function. The format of this field is `projects/{project}/serviceAccounts/{serviceAccountEmail}`

* `environment_variables` - (Optional) A set of key/value environment variable pairs to assign to the function.

* `build_environment_variables` - (Optional) A set of key/value environment variable pairs available during build time.
Expand All @@ -160,7 +162,7 @@ Please refer to the field 'effective_labels' for all of the labels present on th
* `source_archive_object` - (Optional) The source archive object (file) in archive bucket.

* `source_repository` - (Optional) Represents parameters related to source repository where a function is hosted.
Cannot be set alongside `source_archive_bucket` or `source_archive_object`. Structure is [documented below](#nested_source_repository). It must match the pattern `projects/{project}/locations/{location}/repositories/{repository}`.*
Cannot be set alongside `source_archive_bucket` or `source_archive_object`. Structure is [documented below](#nested_source_repository). It must match the pattern `projects/{project}/locations/{location}/repositories/{repository}`.*

* `docker_registry` - (Optional) Docker Registry to use for storing the function's Docker images. Allowed values are ARTIFACT_REGISTRY (default) and CONTAINER_REGISTRY.

Expand Down

0 comments on commit a1b89f3

Please sign in to comment.