Finding the Cloud Tasks location from App Engine

When using the Google Cloud Tasks API you need to specify the project ID and location. It would be good to not hard-code these for your app, and instead determine the values when the application starts or on first using the API.

Code for this post is available on GitHub.

For an App Engine service, the project ID is readily available, both as a runtime environment variable and from the metadata service. The GOOGLE_CLOUD_PROJECT environment variable is your GCP project ID.

The tasks API location is a bit harder to determine. The App Engine region name (us-central, europe-west, etc.) is not exposed as an environment variable, and there’s no end-point for it in the App Engine metadata service.

However on App Engine the GAE_APPLICATION environment variable exposes the appliction ID (same as the project ID) prefixed by a short region code. We can use this cryptic region code to identify a Cloud Tasks API location. For example, all App Engine services deployed in the us-central region have a GAE_APPLICATION value that starts with s~, such as s~dbux-test.

Google’s documentation lists all the App Engine regions, but as far as I know there is no Google documentation for these short region code prefixes. So here is the list of App Engine regions, taken from the gcloud app regions list command, along with the short region prefix that appears when an App Engine application is deployed in each.

App Engine region Short prefix
asia-east1 zde
asia-east2 n
asia-northeast1 b
asia-northeast2 u
asia-northeast3 v
asia-south1 j
asia-southeast1 zas
asia-southeast2 zet
australia-southeast1 f
europe-central2 zlm
europe-west e
europe-west2 g
europe-west3 h
europe-west6 o
northamerica-northeast1 k
southamerica-east1 i
us-central s
us-east1 p
us-east4 d
us-west1 zuw
us-west2 m
us-west3 zwm
us-west4 zwn

N.B. App Engine’s europe-west and us-central region names are equivalent to the Cloud Tasks locations europe-west1 and us-central1 respectively.

So from your Python code you can determine the Cloud Tasks location using this list of short region prefixes.

import os

# Hard-coded list of region prefixes to location names.
    'e': 'europe-west1',  # App Engine region europe-west.
    's': 'us-central1',  # App Engine region us-central.
    'p': 'us-east1',
    'j': 'asia-south1',
    # And others.

def get_project_and_location_for_tasks():
    # This works on App Engine, won't work on Cloud Run.
    app_id = os.environ['GAE_APPLICATION']
    region_code, _, project_id = app_id.partition('~')

    return project_id, REGIONCODES_LOCATIONS[region_code]

Nice! Does feel a little hacky to hard-code that region/locations map. And this won’t handle App Engine regions not in the list.

A more robust solution is to get the location from the Cloud Tasks API. This has the advantage of also working on Cloud Run, but requires 3 more HTTP requests (although those should be super quick). From the command-line, one can use gcloud --project=[project_id] tasks locations list (docs).

The equivalent API method is projects.locations.list.

# pip install google-auth google-api-python-client
import google.auth
import googleapiclient.discovery

def get_project_and_location_for_tasks():
    # Get the project ID from the metadata service. Works on
    # Cloud Run and App Engine.
    _, project_id = google.auth.default()

    name = f'projects/{project_id}'
    service ='cloudtasks', 'v2')
    request = service.projects().locations().list(name=name)
    # Fails if the Cloud Tasks API is not enabled.
    response = request.execute()

    # Grab the first location (there's never more than 1).
    # The response also includes 'name' which is a full location ID like
    # 'projects/[project_id]/locations/[locationId]'.
    first_location = response['locations'][0]

    return project_id, first_location['locationId']

That will fail with an exception if the tasks API is not enabled on the project. When running the application in your local development environment, you will probably want to avoid making requests to public APIs, so that will add some complexity that this code ignores.

The projects.locations.list response is a list of locations. I don’t know if it is currently possible for there to be more than 1 location in the list, the docs suggest that the Cloud Tasks service always follows whichever region the App Engine service is deployed to, and an App Engine service is always tied to 1 region (and cannot change its region later).

What is the location if you use the tasks API from a Cloud Run service, in a project which has never deployed an App Engine service? I don’t know.

I did a test with a project that had an existing App Engine service deployed to region us-central. In the same GCP project I deployed a Cloud Run service in the europe-west1 region, and from the Cloud Run service the call to the projects.locations.list API returned 1 location: us-central1.