Developer Guide

Cloud Custodian is a Python application and supports Python 3 on MacOS, Linux, and Windows. It is recommended using Python 3.7 or higher.

Run the following commands in the root directory after cloning Cloud Custodian:

$ make install
$ source bin/activate

This creates a virtual env in your enlistment and installs all packages as editable.

Now you may run custodian with any flags in order to directly test changes to the source files. For example, custodian schema aws.<resource_type> will return schema for resource type.

Adding New AWS Resources

Create New AWS Resource

Each class definition will use the @resources.register('<resource_name>') decorator to register that class as a Custodian resource substituting <resource_name> with the new resource name. The name specified in the decorator is how the resource will be referenced within policies.

Register the new resource: @resources.register(‘<resource_name>’)

An outer class defining the reference in resource mapping: class <resource_type>(query.QueryResourceManager)

Interior class that defines the aws metadata for resource class resource_type(query.TypeInfo):

class c7n.query.TypeInfo[source]

Resource Type Metadata

Required

Parameters
  • id – Identifier used for apis

  • name – Used for display

  • service – Which aws service (per sdk) has the api for this resource

  • enum_spec – Used to query the resource by describe-sources

Permissions - Optional

Parameters
  • permission_prefix – Permission string prefix if not service

  • permissions_enum – Permissions for resource enumeration/get. Normally we autogen but in some cases we need to specify statically

  • permissions_augment – Permissions for resource augment

Arn handling / generation metadata - Optional

Parameters
  • arn – Arn resource attribute, when describe format has arn

  • arn_type – Type, used for arn construction, also required for universal tag augment

  • arn_separator – How arn type is separated from rest of arn

  • arn_service – For services that need custom labeling for arns

Resource retrieval - Optional

Parameters
  • filter_name – When fetching a single resource via enum_spec this is technically optional, but effectively required for serverless event policies else we have to enumerate the population

  • filter_type – filter_type, scalar or list

  • detail_spec – Used to enrich the resource descriptions returned by enum_spec

  • batch_detail_spec – Used when the api supports getting resource details enmasse

Misc - Optional

Parameters
  • default_report_fields – Used for reporting, array of fields

  • date – Latest date associated to resource, generally references either create date or modified date

  • dimension – Defines that resource has cloud watch metrics and the resource id can be passed as this value. Further customizations of dimensions require subclass metrics filter

  • cfn_type – AWS Cloudformation type

  • config_type – AWS Config Service resource type name

  • universal_taggable – Whether or not resource group tagging api can be used, in which case we’ll automatically register tag actions/filters. Note: values of True will register legacy tag filters/actions, values of object() will just register current standard tag/filters/actions.

  • global_resource – Denotes if this resource exists across all regions (iam, cloudfront, r53)

  • metrics_namespace – Generally we utilize a service to namespace mapping in the metrics filter. However some resources have a type specific namespace (ig. ebs)

  • id_prefix – Specific to ec2 service resources used to disambiguate a resource by its id

An example that adds a new resource:

@resources.register('scaling-policies')
class ScalingPolicies(query.QueryResourceManager):

    # interior class that defines the aws metadata for resource
    class resource_type(query.TypeInfo):
        service = 'autoscaling'
        arn_type = "scalingPolicy"
        id = name = 'PolicyName'
        date = 'CreatedTime'

        # this defines the boto3 call for the resource as well as JMESPATH
        # for accessing TL resources
        enum_spec = (
            'describe_policies', 'ScalingPolicies', None
        )
        filter_name = 'PolicyNames'
        filter_type = 'list'
        cfn_type = config_type = 'AWS::AutoScaling::ScalingPolicy'

Load New AWS Resource

If you created a new module for an AWS service (i.e. this was the first resource implemented for this service in Custodian), then import the new service module in resource_map.py:

"aws.<name of resource>": "c7n.resources.<name of file>.<name of resource class>"

Add New Filter

A filter can be added with a decorator and class:

@<New-resource-class>.filter_registry.register('<filter-name>')

class <NewFilterName>(ValueFilter)

An example that adds a new filter for scaling policies to the ASG resource:

@ASG.filter_registry.register('scaling-policies')
class ScalingPoliciesFilter(ValueFilter):
    schema = type_schema(
        'scaling-policies', rinherit=ValueFilter.schema
    )
    schema_alias = False
    permissions = ("autoscaling:DescribePolicies",)

    def process(self, asgs, event=None):
        self.policy_info = PolicyInfo(self.manager).initialize(asgs)
        return super(ScalingPoliciesFilter, self).process(asgs, event)

    def __call__(self, asg):

        asg_policies = self.policy_info.get(asg)
        matched = False
        if asg_policies is not None:
            for policy in asg_policies:
                matched = self.match(policy) or matched
        return matched

Add New Action

An action can be added with a decorator and class:

@<New-resource-class>.action_registry.register('<action-name>')

class <NewActionName>(Action)

An example that adds a new action for deleting to the ASG resource:

@ASG.action_registry.register('delete')
class Delete(Action):

    schema = type_schema('delete', force={'type': 'boolean'})
    permissions = ("autoscaling:DeleteAutoScalingGroup",)

    def process(self, asgs):
        client = local_session(
            self.manager.session_factory).client('autoscaling')
        for asg in asgs:
            self.process_asg(client, asg)

    def process_asg(self, client, asg):
        force_delete = self.data.get('force', False)
        try:
            self.manager.retry(
                client.delete_auto_scaling_group,
                AutoScalingGroupName=asg['AutoScalingGroupName'],
                ForceDelete=force_delete)
        except ClientError as e:
            if e.response['Error']['Code'] == 'ValidationError':
                return
            raise

Testing

For information regarding testing see testing for developers.