Introduction

I recently had to deploy a scheduled Cloud Build job for a project at work. As this can be easily done through the GCP console, I was expecting to find a cloudbuild_target argument in the google_cloud_scheduler_job resource which unfortunately was not the case^.

The solution

The idea is to define a google_cloud_scheduler_job with a HTTP target since each build trigger comes with its own REST API URL.

The URL takes a triggerId as path parameter, which can be retrieved using the below commands.

1
2
3
$ TRIGGER_NAME=<YOUR_TRIGGER_NAME>
$ gcloud alpha builds triggers list --filter="(name:$TRIGGER_NAME)" --format=json | jq ".[] | .id"
"20cc4c01-aa55-4c44-8d47-XXXXXXXXXXXX"

We also need to take care of authorization to the Google API. Fortunately, google_cloud_scheduler_job provides an oauth_token argument for us to provide details for our authorized service account, which we will grant the following permissions:

  • roles/appengine.appAdmin
  • roles/appengine.appCreator
  • roles/cloudscheduler.admin
  • roles/cloudbuild.builds.builder

We can then define our resources in main.tf as such:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# main.tf

locals {
  # replace values accordingly
  project_id = <YOUR_PROJECT_ID>
  region     = <YOUR_REGION>
  tz         = <YOUR_TIMEZONE>
  trigger_id = <BUILD_TRIGGER_ID> # replace with triggerId retrieved earlier
  branch     = <BUILD_BRANCH>
  schedule   = "0 8 * * *"
}

resource "google_service_account" "cloud_scheduler" {
  account_id   = "cloud-scheduler"
  display_name = "⏱ cloud-scheduler"
  description  = "Service account for scheduling jobs on Cloud Scheduler"
  project      = local.project_id
}

resource "google_project_iam_member" "cloud_scheduler" {
  for_each = toset([
    "roles/appengine.appAdmin",
    "roles/appengine.appCreator",
    "roles/cloudscheduler.admin",
    "roles/cloudbuild.builds.builder",
  ])

  project = local.project_id
  role    = each.key
  member  = "serviceAccount:${google_service_account.cloud_scheduler.email}"
}

resource "google_cloud_scheduler_job" "job" {
  project          = local.project_id
  region           = local.region
  name             = "example-job"
  description      = "sample scheduled build trigger"
  schedule         = local.schedule
  time_zone        = local.tz
  attempt_deadline = "320s"

  http_target {
    http_method = "POST"
    uri         = "https://cloudbuild.googleapis.com/v1/projects/${local.project_id}/triggers/${local.trigger_id}:run"
    body        = base64encode("{\"branchName\":\"${local.branch}\"}")
    headers = {
      "Content-Type" = "application/octet-stream"
      "User-Agent"   = "Google-Cloud-Scheduler"
    }
    oauth_token {
      service_account_email = google_service_account.cloud_scheduler.email
      scope                 = "https://www.googleapis.com/auth/cloud-platform"
    }
  }
}

What our scheduled job looks like after deployment: cloud-sched-1 cloud-sched-2

We can also see that the build trigger runs as intended. Great success! cloud-sched-3

Conclusion

GCP’s extensive APIs makes it easy to schedule any task with an endpoint in Cloud Scheduler. While not intuitive, implementing it via the official provider is definitely possible as demonstrated.

Footnotes

^ accurate as of v4.22.0 of the Hashicorp google provider