Repository: cloud-custodian/cloud-custodian

Issue:
AWS resources with tag restrictions can't fully use OffHours
Hi, I'm setting up a shutdown schedule for RDS and know that RDS tags cannot have the characters needed in them. As a workaround, I am creating a custom policy for RDS with specific values set. The trouble I am facing is the policy I want to implement specifies days to shutdown "off=[(S,1)];on=[(M,6)];tz=it". 

Can the functionality of the Tag be added into the custodian policy.


Discussion:

Patch:
diff --git a/c7n/filters/offhours.py b/c7n/filters/offhours.py
index cd8b02523..1203ccf8b 100644
--- a/c7n/filters/offhours.py
+++ b/c7n/filters/offhours.py
@@ -29,6 +29,9 @@ different policy, they support the same configuration options:
  - **weekends-only**: default false, whether to turn the resource off only on
    the weekend
  - **default_tz**: which timezone to utilize when evaluating time **(REQUIRED)**
+ - **fallback-schedule**: If a resource doesn't support tagging or doesn't provide
+   a tag you can supply a default schedule that will be used. When the tag is provided
+   this will be ignored. See :ref:`ScheduleParser Time Specifications <scheduleparser-time-spec>`.
  - **tag**: which resource tag name to use for per-resource configuration
    (schedule and timezone overrides and opt-in/opt-out); default is
    ``maid_offhours``.
@@ -61,6 +64,7 @@ This example policy overrides most of the defaults for an offhour policy:
            onhour: 8
            offhour: 20
 
+
 Tag Based Configuration
 =======================
 
@@ -94,6 +98,9 @@ The value of the tag must be one of the following:
     supported by :py:class:`c7n.filters.offhours.ScheduleParser` as described
     in the next section.
 
+
+.. _scheduleparser-time-spec:
+
 ScheduleParser Time Specifications
 ----------------------------------
 
@@ -255,6 +262,7 @@ class Time(Filter):
         'properties': {
             'tag': {'type': 'string'},
             'default_tz': {'type': 'string'},
+            'fallback_schedule': {'type': 'string'},
             'weekends': {'type': 'boolean'},
             'weekends-only': {'type': 'boolean'},
             'opt-out': {'type': 'boolean'},
@@ -315,6 +323,7 @@ class Time(Filter):
         self.weekends_only = self.data.get('weekends-only', False)
         self.opt_out = self.data.get('opt-out', False)
         self.tag_key = self.data.get('tag', self.DEFAULT_TAG).lower()
+        self.fallback_schedule = self.data.get('fallback-schedule', None)
         self.default_schedule = self.get_default_schedule()
         self.parser = ScheduleParser(self.default_schedule)
 
@@ -438,12 +447,12 @@ class Time(Filter):
     def get_tag_value(self, i):
         """Get the resource's tag value specifying its schedule."""
         # Look for the tag, Normalize tag key and tag value
-        found = False
+        found = self.fallback_schedule
         for t in i.get('Tags', ()):
             if t['Key'].lower() == self.tag_key:
                 found = t['Value']
                 break
-        if found is False:
+        if found in (False, None):
             return False
         # enforce utf8, or do translate tables via unicode ord mapping
         value = found.lower().encode('utf8').decode('utf8')


Test patch:
diff --git a/tests/test_offhours.py b/tests/test_offhours.py
index d5f1e3906..97539883b 100644
--- a/tests/test_offhours.py
+++ b/tests/test_offhours.py
@@ -320,6 +320,17 @@ class OffHoursFilterTest(BaseTest):
         self.assertTrue(off.parser.keys_are_valid(off.get_tag_value(i)))
         self.assertEqual(off.parser.raw_data(off.get_tag_value(i)), {"tz": "pt"})
 
+    def test_offhours_get_value_fallback(self):
+        sched = "off=[(S,1)];on=[(M,6)];tz=pst"
+        off = OffHour({"default_tz": "ct", "fallback-schedule": sched})
+        i = instance(Tags=[])
+        self.assertEqual(off.get_tag_value(i), sched.lower())
+        self.assertTrue(off.parser.has_resource_schedule(off.get_tag_value(i), "off"))
+        self.assertTrue(off.parser.has_resource_schedule(off.get_tag_value(i), "on"))
+        self.assertTrue(off.parser.keys_are_valid(off.get_tag_value(i)))
+        self.assertEqual(off.parser.raw_data(off.get_tag_value(i)),
+                        {'off': '[(s,1)]', 'on': '[(m,6)]', 'tz': 'pst'})
+
     def test_offhours(self):
         t = datetime.datetime(
             year=2015,