Repository: cloud-custodian/cloud-custodian

Issue:
gcp - usage of gcp-periodic mode in a region other than us-central1
Steps to reproduce:
1. Deploy the following policy:
```yaml
policies:
  - name: broken-periodic
    resource: gcp.instance
    mode:
      type: gcp-periodic
      schedule: "2 * * * *"
      region: europe-west1
```
2. If you don't want to wait, follow to the Cloud Scheduler tab and press Run now
3. Failed

Reason: the deployed Cloud Scheduler job expects the function to be in the specified region (defaults to us-central1 if none). The function is always deployed to the us-central1 (hardcoded)

Fix: #4664



Discussion:

Resolving patch:
diff --git a/tools/c7n_gcp/c7n_gcp/mu.py b/tools/c7n_gcp/c7n_gcp/mu.py
--- a/tools/c7n_gcp/c7n_gcp/mu.py
+++ b/tools/c7n_gcp/c7n_gcp/mu.py
@@ -27,9 +27,6 @@
 log = logging.getLogger('c7n_gcp.mu')
 
 
-DEFAULT_REGION = 'us-central1'
-
-
 def custodian_archive(packages=None):
     if not packages:
         packages = []
@@ -57,7 +54,7 @@ def custodian_archive(packages=None):
 
 class CloudFunctionManager(object):
 
-    def __init__(self, session_factory, region="us-central1"):
+    def __init__(self, session_factory, region):
         self.session_factory = session_factory
         self.session = local_session(session_factory)
         self.client = self.session.client(
@@ -482,9 +479,10 @@ class PeriodicEvent(EventSource):
     https://cloud.google.com/appengine/docs/standard/python/config/cronref
     """
 
-    def __init__(self, session, data):
+    def __init__(self, session, data, region):
         self.session = session
         self.data = data
+        self.region = region
 
     @property
     def target_type(self):
@@ -515,7 +513,7 @@ def add(self, func):
             'create', {
                 'parent': 'projects/{}/locations/{}'.format(
                     self.session.get_default_project(),
-                    self.data.get('region', DEFAULT_REGION)),
+                    self.region),
                 'body': job})
 
     def remove(self, func):
@@ -560,7 +558,7 @@ def get_job_config(self, func, target):
         job = {
             'name': "projects/{}/locations/{}/jobs/{}".format(
                 self.session.get_default_project(),
-                self.data.get('region', DEFAULT_REGION),
+                self.region,
                 self.data.get('name', '{}{}'.format(self.prefix, func.name))),
             'schedule': self.data['schedule'],
             'timeZone': self.data.get('tz', 'Etc/UTC')}
@@ -568,7 +566,7 @@ def get_job_config(self, func, target):
         if self.target_type == 'http':
             job['httpTarget'] = {
                 'uri': 'https://{}-{}.cloudfunctions.net/{}'.format(
-                    self.data.get('region', DEFAULT_REGION),
+                    self.region,
                     self.session.get_default_project(),
                     func.name)
             }
diff --git a/tools/c7n_gcp/c7n_gcp/policy.py b/tools/c7n_gcp/c7n_gcp/policy.py
--- a/tools/c7n_gcp/c7n_gcp/policy.py
+++ b/tools/c7n_gcp/c7n_gcp/policy.py
@@ -21,6 +21,8 @@
 
 from c7n_gcp import mu
 
+DEFAULT_REGION = 'us-central1'
+
 
 class FunctionMode(ServerlessExecutionMode):
 
@@ -31,7 +33,6 @@ class FunctionMode(ServerlessExecutionMode):
            'memory-size': {'type': 'integer'},
            'labels': {'type': 'object'},
            'network': {'type': 'string'},
-           'region': {'type': 'string'},
            'max-instances': {'type': 'integer'},
            'service-account': {'type': 'string'},
            'environment': {'type': 'object'}})
@@ -39,17 +40,18 @@ class FunctionMode(ServerlessExecutionMode):
     def __init__(self, policy):
         self.policy = policy
         self.log = logging.getLogger('custodian.gcp.funcexec')
+        self.region = policy.options.regions[0] if len(policy.options.regions) else DEFAULT_REGION
 
     def run(self):
         raise NotImplementedError("subclass responsibility")
 
     def provision(self):
         self.log.info("Provisioning policy function %s", self.policy.name)
-        manager = mu.CloudFunctionManager(self.policy.session_factory)
+        manager = mu.CloudFunctionManager(self.policy.session_factory, self.region)
         return manager.publish(self._get_function())
 
     def deprovision(self):
-        manager = mu.CloudFunctionManager(self.policy.session_factory)
+        manager = mu.CloudFunctionManager(self.policy.session_factory, self.region)
         return manager.remove(self._get_function())
 
     def validate(self):
@@ -86,7 +88,9 @@ def validate(self):
     def _get_function(self):
         events = [mu.PeriodicEvent(
             local_session(self.policy.session_factory),
-            self.policy.data['mode'])]
+            self.policy.data['mode'],
+            self.region
+        )]
         return mu.PolicyFunction(self.policy, events=events)
 
     def run(self, event, context):


Test patch:
diff --git a/tools/c7n_gcp/tests/test_mu_gcp.py b/tools/c7n_gcp/tests/test_mu_gcp.py
--- a/tools/c7n_gcp/tests/test_mu_gcp.py
+++ b/tools/c7n_gcp/tests/test_mu_gcp.py
@@ -52,7 +52,7 @@ def get_function(self, events=(), factory=None, **kw):
 
     def test_deploy_function(self):
         factory = self.replay_flight_data('mu-deploy')
-        manager = mu.CloudFunctionManager(factory)
+        manager = mu.CloudFunctionManager(factory, 'us-central1')
         func = self.get_function(factory=factory)
         manager.publish(func)
         func_info = manager.get(func.name)