diff --git a/.changelog/99fb6721f2d340ed933d7407c91622ca.md b/.changelog/99fb6721f2d340ed933d7407c91622ca.md new file mode 100644 index 0000000..2ba6b0d --- /dev/null +++ b/.changelog/99fb6721f2d340ed933d7407c91622ca.md @@ -0,0 +1,4 @@ +--- +type: none +--- +Pass at adding AI-assisted API documentation \ No newline at end of file diff --git a/octodns/processor/base.py b/octodns/processor/base.py index 277d0af..d2f3d97 100644 --- a/octodns/processor/base.py +++ b/octodns/processor/base.py @@ -4,93 +4,207 @@ class ProcessorException(Exception): + ''' + Exception raised when a processor encounters an error during processing. + + A subclass of this exception can be raised by processors when they + encounter invalid configurations, unsupported operations, or other + processing errors. + ''' + pass class BaseProcessor(object): + ''' + Base class for all octoDNS processors. + + Processors provide hooks into the octoDNS sync process to modify zones and + records at various stages. They can be used to filter, transform, or + validate DNS records before planning and applying changes. + + Processors are executed in the order they are configured and can modify: + + - **Source zones** (after sources populate, before planning) + - **Target zones** (after target populates, before planning) + - **Source and target zones** (just before computing changes) + - **Plans** (after planning, before applying) + + Subclasses should override one or more of the ``process_*`` methods to + implement custom processing logic. + + Example usage:: + + processors: + my-processor: + class: my.custom.processor.MyProcessor + # processor-specific configuration + + zones: + example.com.: + sources: + - config + processors: + - my-processor + targets: + - route53 + + See Also: + - :class:`octodns.processor.filter.TypeAllowlistFilter` + - :class:`octodns.processor.ownership.OwnershipProcessor` + - :class:`octodns.processor.acme.AcmeMangingProcessor` + ''' + def __init__(self, name): + ''' + Initialize the processor. + + :param name: Unique identifier for this processor instance. Used in + logging and configuration references. + :type name: str + + .. note:: + The ``name`` parameter is deprecated and will be removed in + version 2.0. Use ``id`` instead. + ''' # TODO: name is DEPRECATED, remove in 2.0 self.id = self.name = name def process_source_zone(self, desired, sources): ''' + Process the desired zone after all sources have populated. + Called after all sources have completed populate. Provides an - opportunity for the processor to modify the desired `Zone` that targets + opportunity for the processor to modify the desired zone that targets will receive. - - Will see `desired` after any modifications done by - `Provider._process_desired_zone` and processors configured to run - before this one. - - May modify `desired` directly. - - Must return `desired` which will normally be the `desired` param. - - Must not modify records directly, `record.copy` should be called, - the results of which can be modified, and then `Zone.add_record` may - be used with `replace=True`. - - May call `Zone.remove_record` to remove records from `desired`. - - Sources may be empty, as will be the case for aliased zones. + :param desired: The desired zone state after all sources have populated. + This zone will be used as the target state for planning. + :type desired: octodns.zone.Zone + :param sources: List of source providers that populated the zone. May be + empty for aliased zones. + :type sources: list[octodns.provider.base.BaseProvider] + + :return: The modified desired zone, typically the same object passed in. + :rtype: octodns.zone.Zone + + .. important:: + - Will see ``desired`` after any modifications done by + ``Provider._process_desired_zone`` and processors configured to run + before this one. + - May modify ``desired`` directly. + - Must return ``desired`` which will normally be the ``desired`` param. + - Must not modify records directly; ``record.copy`` should be called, + the results of which can be modified, and then ``Zone.add_record`` + may be used with ``replace=True``. + - May call ``Zone.remove_record`` to remove records from ``desired``. + - Sources may be empty, as will be the case for aliased zones. ''' return desired def process_target_zone(self, existing, target): ''' - Called after a target has completed `populate`, before changes are - computed between `existing` and `desired`. This provides an opportunity - to modify the `existing` `Zone`. - - - Will see `existing` after any modifications done by processors - configured to run before this one. - - May modify `existing` directly. - - Must return `existing` which will normally be the `existing` param. - - Must not modify records directly, `record.copy` should be called, - the results of which can be modified, and then `Zone.add_record` may - be used with `replace=True`. - - May call `Zone.remove_record` to remove records from `existing`. + Process the existing zone after the target has populated. + + Called after a target has completed ``populate``, before changes are + computed between ``existing`` and ``desired``. This provides an + opportunity to modify the existing zone state. + + :param existing: The current zone state from the target provider. + :type existing: octodns.zone.Zone + :param target: The target provider that populated the existing zone. + :type target: octodns.provider.base.BaseProvider + + :return: The modified existing zone, typically the same object passed in. + :rtype: octodns.zone.Zone + + .. important:: + - Will see ``existing`` after any modifications done by processors + configured to run before this one. + - May modify ``existing`` directly. + - Must return ``existing`` which will normally be the ``existing`` param. + - Must not modify records directly; ``record.copy`` should be called, + the results of which can be modified, and then ``Zone.add_record`` + may be used with ``replace=True``. + - May call ``Zone.remove_record`` to remove records from ``existing``. ''' return existing def process_source_and_target_zones(self, desired, existing, target): ''' - Called just prior to computing changes for ``target`` between - ``desired`` and `existing`. Provides an opportunity for the processor - to modify either the desired or existing ``Zone`` that will be used to - compute the changes and create the initial plan. - - - Will see ``desired`` after any modifications done by - ``Provider._process_desired_zone`` and all processors via - ``Processor.process_source_zone`` - - Will see ``existing`` after any modifications done by all processors - via ``Processor.process_target_zone`` - - Will see both ``desired`` and ``existing`` after any modifications - done by any processors configured to run before this one via - ``Processor.process_source_and_target_zones``. - - May modify ``desired`` directly. - - Must return ``desired`` which will normally be the ``desired`` param. - - May modify ``existing`` directly. - - Must return ``existing`` which will normally be the ``existing`` - param. - - Must not modify records directly, ``record.copy`` should be called, - the results of which can be modified, and then ``Zone.add_record`` - may be used with ``replace=True``. - - May call ``Zone.remove_record`` to remove records from ``desired``. - - May call ``Zone.remove_record`` to remove records from ``existing``. + Process both desired and existing zones before computing changes. + + Called just prior to computing changes for the target provider between + ``desired`` and ``existing``. Provides an opportunity for the processor + to modify either or both zones that will be used to compute the changes + and create the initial plan. + + :param desired: The desired zone state after all source processing. + :type desired: octodns.zone.Zone + :param existing: The existing zone state after all target processing. + :type existing: octodns.zone.Zone + :param target: The target provider for which changes will be computed. + :type target: octodns.provider.base.BaseProvider + + :return: A tuple of (desired, existing) zones, typically the same + objects passed in. + :rtype: tuple[octodns.zone.Zone, octodns.zone.Zone] + + .. important:: + - Will see ``desired`` after any modifications done by + ``Provider._process_desired_zone`` and all processors via + ``Processor.process_source_zone``. + - Will see ``existing`` after any modifications done by all processors + via ``Processor.process_target_zone``. + - Will see both ``desired`` and ``existing`` after any modifications + done by any processors configured to run before this one via + ``Processor.process_source_and_target_zones``. + - May modify ``desired`` directly. + - Must return ``desired`` which will normally be the ``desired`` param. + - May modify ``existing`` directly. + - Must return ``existing`` which will normally be the ``existing`` + param. + - Must not modify records directly; ``record.copy`` should be called, + the results of which can be modified, and then ``Zone.add_record`` + may be used with ``replace=True``. + - May call ``Zone.remove_record`` to remove records from ``desired``. + - May call ``Zone.remove_record`` to remove records from ``existing``. ''' return desired, existing def process_plan(self, plan, sources, target): ''' + Process the plan after it has been computed. + Called after the planning phase has completed. Provides an opportunity - for the processors to modify the plan thus changing the actions that + for the processor to modify the plan, thus changing the actions that will be displayed and potentially applied. - - `plan` may be None if no changes were detected, if so a `Plan` may - still be created and returned. - - May modify `plan.changes` directly or create a new `Plan`. - - Does not have to modify `plan.desired` and/or `plan.existing` to line - up with any modifications made to `plan.changes`. - - Should copy over `plan.exists`, `plan.update_pcent_threshold`, and - `plan.delete_pcent_threshold` when creating a new `Plan`. - - Must return a `Plan` which may be `plan` or can be a newly created - one `plan.desired` and `plan.existing` copied over as-is or modified. + :param plan: The computed plan containing the changes to be applied. + May be None if no changes were detected. + :type plan: octodns.provider.plan.Plan or None + :param sources: List of source providers for this zone. May be empty + for aliased zones. + :type sources: list[octodns.provider.base.BaseProvider] + :param target: The target provider for which the plan was created. + :type target: octodns.provider.base.BaseProvider + + :return: The modified plan, which may be the same object passed in, + a newly created Plan, or None if no changes are needed. + :rtype: octodns.provider.plan.Plan or None + + .. important:: + - ``plan`` may be None if no changes were detected; if so, a ``Plan`` + may still be created and returned. + - May modify ``plan.changes`` directly or create a new ``Plan``. + - Does not have to modify ``plan.desired`` and/or ``plan.existing`` to + line up with any modifications made to ``plan.changes``. + - Should copy over ``plan.exists``, ``plan.update_pcent_threshold``, + and ``plan.delete_pcent_threshold`` when creating a new ``Plan``. + - Must return a ``Plan`` which may be ``plan`` or can be a newly + created one with ``plan.desired`` and ``plan.existing`` copied over + as-is or modified. + - Sources may be empty, as will be the case for aliased zones. ''' # plan may be None if no changes were detected up until now, the # process may still create a plan. diff --git a/octodns/provider/base.py b/octodns/provider/base.py index b089ffd..8910131 100644 --- a/octodns/provider/base.py +++ b/octodns/provider/base.py @@ -9,6 +9,56 @@ from .plan import Plan class BaseProvider(BaseSource): + ''' + Base class for all octoDNS providers. + + Providers extend :class:`octodns.source.base.BaseSource` to add the ability + to apply DNS changes to a target system. While sources only need to + implement ``populate()`` to read DNS data, providers also implement + ``plan()`` and ``apply()`` to manage the complete sync workflow. + + The provider workflow: + + 1. **Populate**: Load current state from the provider via ``populate()`` + 2. **Process**: Modify zones through ``_process_desired_zone()`` and + ``_process_existing_zone()`` to handle provider-specific limitations + 3. **Plan**: Compute changes between desired and existing state via ``plan()`` + 4. **Apply**: Submit approved changes to the provider via ``apply()`` + + Subclasses must implement: + + - **_apply(plan)**: Actually submit changes to the provider's API/backend + + Subclasses should override as needed: + + - **_process_desired_zone(desired)**: Modify desired state before planning + - **_process_existing_zone(existing, desired)**: Modify existing state before planning + - **_include_change(change)**: Filter out false positive changes + - **_extra_changes(existing, desired, changes)**: Add provider-specific changes + - **_plan_meta(existing, desired, changes)**: Add metadata to the plan + + Example provider configuration:: + + providers: + route53: + class: octodns_route53.Route53Provider + access_key_id: env/AWS_ACCESS_KEY_ID + secret_access_key: env/AWS_SECRET_ACCESS_KEY + + zones: + example.com.: + sources: + - config + targets: + - route53 + + See Also: + - :class:`octodns.source.base.BaseSource` + - :class:`octodns.provider.plan.Plan` + - :class:`octodns.provider.yaml.YamlProvider` + - :doc:`/zone_lifecycle` + ''' + def __init__( self, id, @@ -18,6 +68,30 @@ class BaseProvider(BaseSource): strict_supports=True, root_ns_warnings=True, ): + ''' + Initialize the provider. + + :param id: Unique identifier for this provider instance. + :type id: str + :param apply_disabled: If True, the provider will plan changes but not + apply them. Useful for read-only/validation mode. + :type apply_disabled: bool + :param update_pcent_threshold: Maximum percentage of existing records + that can be updated in one sync before + requiring ``--force``. Default: 0.3 (30%). + :type update_pcent_threshold: float + :param delete_pcent_threshold: Maximum percentage of existing records + that can be deleted in one sync before + requiring ``--force``. Default: 0.3 (30%). + :type delete_pcent_threshold: float + :param strict_supports: If True, raise exceptions when unsupported + features are encountered. If False, log warnings + and attempt to work around limitations. + :type strict_supports: bool + :param root_ns_warnings: If True, log warnings about root NS record + handling. If False, silently handle root NS. + :type root_ns_warnings: bool + ''' super().__init__(id) self.log.debug( '__init__: id=%s, apply_disabled=%s, ' @@ -40,21 +114,33 @@ class BaseProvider(BaseSource): def _process_desired_zone(self, desired): ''' - An opportunity for providers to modify the desired zone records before - planning. `desired` is a "shallow" copy, see `Zone.copy` for more - information - - - Must call `super` at an appropriate point for their work, generally - that means as the final step of the method, returning the result of - the `super` call. - - May modify `desired` directly. - - Must not modify records directly, `record.copy` should be called, - the results of which can be modified, and then `Zone.add_record` may - be used with `replace=True`. - - May call `Zone.remove_record` to remove records from `desired`. - - Must call supports_warn_or_except with information about any changes - that are made to have them logged or throw errors depending on the - provider configuration. + Process the desired zone before planning. + + Called during the planning phase to modify the desired zone records + before changes are computed. This is where providers handle their + limitations by removing or modifying records that aren't supported. The + parent method will deal with "standard" unsupported cases like types, + dynamic, and root NS handling. The ``desired`` zone is a shallow copy + (see :meth:`octodns.zone.Zone.copy`). + + :param desired: The desired zone state to be processed. This is a shallow + copy that can be modified. + :type desired: octodns.zone.Zone + + :return: The processed desired zone, typically the same object passed in. + :rtype: octodns.zone.Zone + + .. important:: + - Must call ``super()`` at an appropriate point, generally as the + final step of the method, returning the result of the super call. + - May modify ``desired`` directly. + - Must not modify records directly; ``record.copy()`` should be called, + the results of which can be modified, and then ``Zone.add_record()`` + may be used with ``replace=True``. + - May call ``Zone.remove_record()`` to remove records from ``desired``. + - Must call :meth:`supports_warn_or_except` with information about any + changes that are made to have them logged or throw errors depending + on the provider configuration. ''' for record in desired.records: @@ -178,22 +264,35 @@ class BaseProvider(BaseSource): def _process_existing_zone(self, existing, desired): ''' - An opportunity for providers to modify the existing zone records before - planning. `existing` is a "shallow" copy, see `Zone.copy` for more - information - - - `desired` must not be modified in anyway, it is only for reference - - Must call `super` at an appropriate point for their work, generally - that means as the final step of the method, returning the result of - the `super` call. - - May modify `existing` directly. - - Must not modify records directly, `record.copy` should be called, - the results of which can be modified, and then `Zone.add_record` may - be used with `replace=True`. - - May call `Zone.remove_record` to remove records from `existing`. - - Must call supports_warn_or_except with information about any changes - that are made to have them logged or throw errors depending on the - provider configuration. + Process the existing zone before planning. + + Called during the planning phase to modify the existing zone records + before changes are computed. This allows providers to normalize or filter + the current state from the provider. The ``existing`` zone is a shallow + copy (see :meth:`octodns.zone.Zone.copy`). + + :param existing: The existing zone state from the provider. This is a + shallow copy that can be modified. + :type existing: octodns.zone.Zone + :param desired: The desired zone state. This is for reference only and + must not be modified. + :type desired: octodns.zone.Zone + + :return: The processed existing zone, typically the same object passed in. + :rtype: octodns.zone.Zone + + .. important:: + - ``desired`` must not be modified in any way; it is only for reference. + - Must call ``super()`` at an appropriate point, generally as the + final step of the method, returning the result of the super call. + - May modify ``existing`` directly. + - Must not modify records directly; ``record.copy()`` should be called, + the results of which can be modified, and then ``Zone.add_record()`` + may be used with ``replace=True``. + - May call ``Zone.remove_record()`` to remove records from ``existing``. + - Must call :meth:`supports_warn_or_except` with information about any + changes that are made to have them logged or throw errors depending + on the provider configuration. ''' existing_root_ns = existing.root_ns @@ -211,35 +310,116 @@ class BaseProvider(BaseSource): def _include_change(self, change): ''' - An opportunity for providers to filter out false positives due to - peculiarities in their implementation. E.g. minimum TTLs. + Filter out false positive changes. + + Called during planning to allow providers to filter out changes that + are false positives due to peculiarities in their implementation (e.g., + providers that enforce minimum TTLs). + + :param change: A change being considered for inclusion in the plan. + :type change: octodns.record.change.Change + + :return: True if the change should be included in the plan, False to + filter it out. + :rtype: bool ''' return True def _extra_changes(self, existing, desired, changes): ''' - An opportunity for providers to add extra changes to the plan that are - necessary to update ancillary record data or configure the zone. E.g. - base NS records. + Add provider-specific extra changes to the plan. + + Called during planning to allow providers to add extra changes that are + necessary to update ancillary record data or configure the zone (e.g., + base NS records that must be managed separately). + + :param existing: The existing zone state. + :type existing: octodns.zone.Zone + :param desired: The desired zone state. + :type desired: octodns.zone.Zone + :param changes: The list of changes already computed. + :type changes: list[octodns.record.change.Change] + + :return: A list of additional changes to add to the plan. Return an + empty list if no extra changes are needed. + :rtype: list[octodns.record.change.Change] ''' return [] def _plan_meta(self, existing, desired, changes): ''' - An opportunity for providers to indicate they have "meta" changes - to the zone which are unrelated to records. Examples may include service - plan changes, replication settings, and notes. The returned data is - arbitrary/opaque to octoDNS, with the only requirement being that - pprint.pformat can display it. A dict is recommended. + Indicate provider-specific metadata changes to the zone. + + Called during planning to allow providers to indicate they have "meta" + changes to the zone which are unrelated to records. Examples may include + service plan changes, replication settings, and notes. + + :param existing: The existing zone state. + :type existing: octodns.zone.Zone + :param desired: The desired zone state. + :type desired: octodns.zone.Zone + :param changes: The list of changes computed for this plan. + :type changes: list[octodns.record.change.Change] + + :return: Arbitrary metadata about zone-level changes. The only + requirement is that ``pprint.pformat`` can display it. A dict + is recommended. Return None if no meta changes. + :rtype: dict or None ''' return None def supports_warn_or_except(self, msg, fallback): + ''' + Handle unsupported features based on strict_supports setting. + + If ``strict_supports`` is True, raises a :class:`SupportsException`. + Otherwise, logs a warning with the message and fallback behavior. + + :param msg: Description of the unsupported feature or limitation. + :type msg: str + :param fallback: Description of the fallback behavior being used. + :type fallback: str + + :raises SupportsException: If ``strict_supports`` is True. + ''' if self.strict_supports: raise SupportsException(f'{self.id}: {msg}') self.log.warning('%s; %s', msg, fallback) def plan(self, desired, processors=[]): + ''' + Compute a plan of changes needed to sync the desired state to this provider. + + This is the main planning method that orchestrates the entire planning + workflow. It populates the current state, processes both desired and + existing zones, runs processors, computes changes, and returns a + :class:`Plan` object. + + The planning workflow: + + 1. Populate existing state from the provider via :meth:`populate` + 2. Process desired zone via :meth:`_process_desired_zone` + 3. Process existing zone via :meth:`_process_existing_zone` + 4. Run target zone processors + 5. Run source and target zone processors + 6. Compute changes between existing and desired + 7. Filter changes via :meth:`_include_change` + 8. Add extra changes via :meth:`_extra_changes` + 9. Add metadata via :meth:`_plan_meta` + 10. Create and return a Plan (or None if no changes) + + :param desired: The desired zone state to sync to this provider. + :type desired: octodns.zone.Zone + :param processors: List of processors to run during planning. + :type processors: list[octodns.processor.base.BaseProcessor] + + :return: A Plan containing the computed changes, or None if no changes + are needed. + :rtype: octodns.provider.plan.Plan or None + + See Also: + - :doc:`/zone_lifecycle` for details on the complete sync workflow + ''' self.log.info('plan: desired=%s', desired.decoded_name) existing = Zone(desired.name, desired.sub_zones) @@ -310,8 +490,21 @@ class BaseProvider(BaseSource): def apply(self, plan): ''' - Submits actual planned changes to the provider. Returns the number of - changes made + Apply the planned changes to the provider. + + This is the main apply method that submits the approved plan to the + provider's backend. If ``apply_disabled`` is True, this method does + nothing and returns 0. + + :param plan: The plan containing changes to apply. + :type plan: octodns.provider.plan.Plan + + :return: The number of changes that were applied. + :rtype: int + + See Also: + - :meth:`_apply` for the provider-specific implementation + - :doc:`/zone_lifecycle` for details on the complete sync workflow ''' if self.apply_disabled: self.log.info('apply: disabled') @@ -324,4 +517,28 @@ class BaseProvider(BaseSource): return len(plan.changes) def _apply(self, plan): + ''' + Actually submit the changes to the provider's backend. + + This is an abstract method that must be implemented by all provider + subclasses. It should take the changes in the plan and apply them to + the provider's API or backend system. + + :param plan: The plan containing changes to apply. + :type plan: octodns.provider.plan.Plan + + :raises NotImplementedError: This base class method must be overridden + by subclasses. + + .. important:: + - Must implement the actual logic to submit changes to the provider. + - Should handle errors appropriately (log, raise exceptions, etc.). + - May apply changes in any order that makes sense for the provider + with as much safety as possible given the API methods available. + Often the order of changes should apply deletes before adds to + avoid comflicts during type changes, specidically **CNAME** <-> + other types. If the provider's API supports batching or atomic + changes they should be used. + - Should be idempotent where possible. + ''' raise NotImplementedError('Abstract base class, _apply method missing') diff --git a/octodns/source/base.py b/octodns/source/base.py index d790213..40cec09 100644 --- a/octodns/source/base.py +++ b/octodns/source/base.py @@ -4,6 +4,48 @@ class BaseSource(object): + ''' + Base class for all octoDNS sources and providers. + + Sources are responsible for loading DNS records from various backends into + octoDNS zones. They implement the ``populate`` method to read DNS data from + their respective data stores (YAML files, APIs, databases, etc.) and add + records to the provided zone. + + Subclasses must define the following class attributes either statically or + prior to calling ``super().__init__``: + + - **SUPPORTS**: Set of supported record types (e.g., ``{'A', 'AAAA', 'CNAME'}``) + - **SUPPORTS_GEO**: Boolean indicating if the source supports GeoDNS records + - **log**: Logger instance for the source + + Optional class attributes: + + - **SUPPORTS_MULTIVALUE_PTR**: Support for multiple PTR records (default: False) + - **SUPPORTS_POOL_VALUE_STATUS**: Support for pool value status flags (default: False) + - **SUPPORTS_ROOT_NS**: Support for root NS records (default: False) + - **SUPPORTS_DYNAMIC_SUBNETS**: Support for dynamic subnet-based routing (default: False) + + Example usage:: + + sources: + config: + class: octodns.provider.yaml.YamlProvider + directory: ./config + + zones: + example.com.: + sources: + - config + targets: + - route53 + + See Also: + - :class:`octodns.provider.yaml.YamlProvider` + - :class:`octodns.source.tinydns.TinyDnsFileSource` + - :class:`octodns.source.envvar.EnvVarSource` + ''' + SUPPORTS_MULTIVALUE_PTR = False SUPPORTS_POOL_VALUE_STATUS = False SUPPORTS_ROOT_NS = False @@ -11,8 +53,15 @@ class BaseSource(object): def __init__(self, id): ''' - :param id: unique identifier for the provider or source + Initialize the source. + + :param id: Unique identifier for this source instance. Used in logging + and configuration references. :type id: str + + :raises NotImplementedError: If required class attributes (``log``, + ``SUPPORTS_GEO``, or ``SUPPORTS``) are not + defined in the subclass. ''' self.id = id @@ -31,29 +80,75 @@ class BaseSource(object): @property def SUPPORTS_DYNAMIC(self): + ''' + Indicates whether this source supports dynamic records. + + Dynamic records include advanced routing features like GeoDNS pools, + health checks, and weighted responses. Most sources do not support + dynamic records. + + :return: True if dynamic records are supported, False otherwise. + :rtype: bool + ''' return False def populate(self, zone, target=False, lenient=False): ''' - Loads all records the provider knows about for the provided zone + Load DNS records from the source into the provided zone. - When `target` is True the populate call is being made to load the - current state of the provider. + This method is responsible for reading DNS data from the source's + backend and adding records to the zone using ``zone.add_record()``. + Subclasses must implement this method. - When `lenient` is True the populate call may skip record validation and - do a "best effort" load of data. That will allow through some common, - but not best practices stuff that we otherwise would reject. E.g. no - trailing . or missing escapes for ;. + :param zone: The zone to populate with records from this source. + :type zone: octodns.zone.Zone + :param target: If True, the populate call is loading the current state + from a target provider (for comparison during sync). If + False, loading desired state from a source. + :type target: bool + :param lenient: If True, skip strict record validation and do a "best + effort" load of data. This allows some non-best-practice + configurations through (e.g., missing trailing dots or + unescaped semicolons). + :type lenient: bool - When target is True (loading current state) this method should return - True if the zone exists or False if it does not. + :return: When ``target`` is True (loading current state), should return + True if the zone exists in the target or False if it does not. + When ``target`` is False (loading desired state), return value + is ignored and may be None. + :rtype: bool or None + + :raises NotImplementedError: This base class method must be overridden + by subclasses. + + .. important:: + - Must use ``zone.add_record()`` to add records to the zone. + - Should not modify the zone name or other zone properties. + - When ``target=True``, must return a boolean indicating zone existence. + - When ``lenient=True``, should relax validation to handle common + non-standard configurations. ''' raise NotImplementedError( 'Abstract base class, populate method missing' ) def supports(self, record): + ''' + Check if this source supports the given record type. + + :param record: The DNS record to check for support. + :type record: octodns.record.base.Record + + :return: True if the record type is supported, False otherwise. + :rtype: bool + ''' return record._type in self.SUPPORTS def __repr__(self): + ''' + Return a string representation of this source. + + :return: The class name of this source instance. + :rtype: str + ''' return self.__class__.__name__