Repository: cloud-custodian/cloud-custodian

Issue:
aws.app-elb and aws.elb missing auto-tag-user action
**Describe the bug**
The aws.app-elb and aws.elb resources no longer show `auto-tag-user` as an available action.  This seems like it might be a result of the refactor related to #5032.

**To Reproduce**
Steps to reproduce the behavior:
1. install latest git version of custodian
2. `custodian schema aws.app-elb.actions`

**Expected behavior**
`auto-tag-user` should be an available action.

**Background (please complete the following information):**
 - Python Version: 3.7.6
 - Custodian Version: 0.8.46.1.dev37+gf4980b758
 - Cloud Provider: aws
 - Policy: [please exclude any account/sensitive information]
```yaml
policies:
  - name: app-elb-creator
    resource: app-elb
    actions:
      - type: auto-tag-user
        tag: Creator
```
 - Traceback: [if applicable, please exclude sensitive/account information]
```
$ custodian validate a.yaml
2020-02-26 11:23:16,591: custodian.commands:ERROR Configuration invalid: a.yaml
2020-02-26 11:23:16,591: custodian.commands:ERROR Error on policy:app-elb-creator resource:app-elb
{'type': 'auto-tag-user', 'tag': 'Creator'} is not valid under any of the given schemas

Failed validating 'anyOf' in schema[16]['allOf'][1]['properties']['actions']['items']:
    {'anyOf': [{'$ref': '#/definitions/resources/aws.app-elb/actions/delete'},
               {'$ref': '#/definitions/actions/aws.invoke-lambda'},
               {'$ref': '#/definitions/actions/aws.invoke-sfn'},
               {'$ref': '#/definitions/actions/aws.mark-for-op'},
               {'$ref': '#/definitions/resources/aws.app-elb/actions/modify-listener'},
               {'$ref': '#/definitions/actions/aws.modify-security-groups'},
               {'$ref': '#/definitions/actions/aws.notify'},
               {'$ref': '#/definitions/actions/aws.post-finding'},
               {'$ref': '#/definitions/actions/aws.post-item'},
               {'$ref': '#/definitions/actions/aws.put-metric'},
               {'$ref': '#/definitions/actions/aws.remove-tag'},
               {'$ref': '#/definitions/resources/aws.app-elb/actions/set-s3-logging'},
               {'$ref': '#/definitions/resources/aws.app-elb/actions/set-shield'},
               {'$ref': '#/definitions/resources/aws.app-elb/actions/set-waf'},
               {'$ref': '#/definitions/actions/aws.tag'},
               {'$ref': '#/definitions/actions/aws.webhook'},
               {'enum': ['notify',
                         'webhook',
                         'invoke-lambda',
                         'put-metric',
                         'post-finding',
                         'invoke-sfn',
                         'post-item',
                         'set-shield',
                         'set-waf',
                         'set-s3-logging',
                         'mark-for-op',
                         'tag',
                         'remove-tag',
                         'delete',
                         'modify-listener',
                         'modify-security-groups']}]}

On instance['actions'][0]:
    {'tag': 'Creator', 'type': 'auto-tag-user'}
2020-02-26 11:23:16,592: custodian.commands:ERROR app-elb-creator
```
**Additional context**
Add any other context about the problem here.



Discussion:
Actually, looking more closely, this is missing from other resource types too ... `asg`, `emr`, `redshift`, `s3` are others that are known to be missing it.
hmm.. thanks, looking now, its possibly an import ordering issue for the provider, we need to explicitly import generic stuff (actions/filters) in the resource loader for it to register with the lazy loaded resources.
Didn’t see cause in a 10m debug session, going to look again tomorrow
okay, I found the issue. we used to call resources type subscribers at different times when loading resources including at the end of resource loading, the new registry subscriber semantic does away with subscribing at different lifecycle times, because with lazy loading there notionally isn't all resources done loading signal. instead subscribers are re-entrant and called as resources are loaded lazily. specifically for auto-tag-user we need the action resource type subscriber to be called after the tag actions are registered. I'll update the resource loading for each resource to re-invoke subscribers

Resolving patch:
diff --git a/c7n/provider.py b/c7n/provider.py
--- a/c7n/provider.py
+++ b/c7n/provider.py
@@ -64,7 +64,11 @@ def get_session_factory(self, options):
     @classmethod
     def get_resource_types(cls, resource_types):
         """Return the resource classes for the given type names"""
-        return import_resource_classes(cls.resource_map, resource_types)
+        resource_classes, not_found = import_resource_classes(
+            cls.resource_map, resource_types)
+        for r in resource_classes:
+            cls.resources.notify(r)
+        return resource_classes, not_found
 
 
 def import_resource_classes(resource_map, resource_types):
@@ -73,11 +77,12 @@ def import_resource_classes(resource_map, resource_types):
 
     mod_map = {}
     rmods = set()
-    not_found = []
+    not_found = set()
+    found = []
 
     for r in resource_types:
         if r not in resource_map:
-            not_found.append(r)
+            not_found.add(r)
             continue
         rmodule, rclass = resource_map[r].rsplit('.', 1)
         rmods.add(rmodule)
@@ -85,10 +90,16 @@ def import_resource_classes(resource_map, resource_types):
     for rmodule in rmods:
         mod_map[rmodule] = importlib.import_module(rmodule)
 
-    return [getattr(mod_map[rmodule], rclass, None) for
-            rmodule, rclass in [
-                resource_map[r].rsplit('.', 1) for r in resource_types
-                if r in resource_map]], not_found
+    for rtype in resource_types:
+        if rtype in not_found:
+            continue
+        rmodule, rclass = resource_map[rtype].rsplit('.', 1)
+        r = getattr(mod_map[rmodule], rclass, None)
+        if r is None:
+            not_found.add(rtype)
+        else:
+            found.append(r)
+    return found, list(not_found)
 
 
 # nosetests seems to think this function is a test
diff --git a/c7n/registry.py b/c7n/registry.py
--- a/c7n/registry.py
+++ b/c7n/registry.py
@@ -56,8 +56,6 @@ def __init__(self, plugin_type):
 
     def subscribe(self, func):
         self._subscribers.append(func)
-        for p in self.values():
-            func(self, p)
 
     def register(self, name, klass=None, condition=True,
                  condition_message="Missing dependency for {}",
@@ -69,7 +67,6 @@ def register(self, name, klass=None, condition=True,
             klass.type = name
             klass.type_aliases = aliases
             self._factories[name] = klass
-            self.notify(klass)
             return klass
 
         # invoked as class decorator
@@ -79,7 +76,6 @@ def _register_class(klass):
             self._factories[name] = klass
             klass.type = name
             klass.type_aliases = aliases
-            self.notify(klass)
             return klass
         return _register_class
 
diff --git a/tools/c7n_gcp/c7n_gcp/resources/resource_map.py b/tools/c7n_gcp/c7n_gcp/resources/resource_map.py
--- a/tools/c7n_gcp/c7n_gcp/resources/resource_map.py
+++ b/tools/c7n_gcp/c7n_gcp/resources/resource_map.py
@@ -64,7 +64,6 @@
     "gcp.log-exclusion": "c7n_gcp.resources.logging.LogExclusion",
     "gcp.log-project-metric": "c7n_gcp.resources.logging.LogProjectMetric",
     "gcp.log-project-sink": "c7n_gcp.resources.logging.LogProjectSink",
-    "gcp.logsink": "c7n_gcp.resources.logging.LogSink",
     "gcp.ml-job": "c7n_gcp.resources.mlengine.MLJob",
     "gcp.ml-model": "c7n_gcp.resources.mlengine.MLModel",
     "gcp.organization": "c7n_gcp.resources.resourcemanager.Organization",


Test patch:
diff --git a/tests/test_registry.py b/tests/test_registry.py
--- a/tests/test_registry.py
+++ b/tests/test_registry.py
@@ -57,7 +57,11 @@ class _plugin_impl1:
         class _plugin_impl2:
             pass
 
-        self.assertEqual(observed[0], (registry, _plugin_impl1))
+        self.assertEqual(observed, [])
+
+        registry.notify(_plugin_impl1)
+        registry.notify(_plugin_impl2)
+
         self.assertEqual(observed[1], (registry, _plugin_impl2))
         self.assertEqual(list(sorted(registry.keys())), ['hot', 'water'])
 
diff --git a/tests/test_tags.py b/tests/test_tags.py
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -26,7 +26,18 @@
 from .common import BaseTest
 
 
-class UniversalAugmentTest(BaseTest):
+class UniversalTagTest(BaseTest):
+
+    def test_auto_tag_registration(self):
+        try:
+            self.load_policy({
+                'name': 'sfn-auto',
+                'resource': 'step-machine',
+                'mode': {'type': 'cloudtrail',
+                         'events': [{'ids': 'some', 'source': 'thing', 'event': 'wicked'}]},
+                'actions': [{'type': 'auto-tag-user', 'tag': 'creator'}]})
+        except Exception as e:
+            self.fail('auto-tag policy failed to load %s' % e)
 
     def test_universal_augment_resource_missing_tags(self):
         session_factory = self.replay_flight_data('test_tags_universal_augment_missing_tags')
@@ -47,9 +58,6 @@ def test_universal_augment_resource_missing_tags(self):
         results = policy.run()
         self.assertTrue('Tags' in results[0])
 
-
-class UniversalTagRetry(BaseTest):
-
     def test_retry_no_error(self):
         mock = MagicMock()
         mock.side_effect = [{"Result": 42}]