Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/user/app_feature_remediation.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The type of remediation to be performed in a particular platform is defined by n
Network device operating systems (Nautobot Platforms) can consume two different types of remediation, namely:

- **HIERCONFIG remediation (CLI - hierarchical)**
- **Dynamic HIERCONFIG remediation**
- **Custom Remediation**

![Remediation Platform Settings](../images/remediation_settings_per_platform.png)
Expand All @@ -48,6 +49,33 @@ Default Hier config options can be used or customized on a per platform basis, a
For additional information on how to customize Hier Config options, please refer to the Hierarchical Configuration development guide:
https://netdevops.io/hier_config/advanced-topics/

### Dynamic Hier Config Remediation Type

The Dynamic Hier Config remediation option is an additional lever to customize the default Hier Config remediation config without the need to rewrite the entire Hier Config flow in a custom remediation function. Dynamic remeditaion takes the form of modules imported from a Git repository. Specifically within the root folder of the repository, there should be a folder called `hier_config_dynamic_remediations`, and within that folder should be an `__init__.py` and any dynamic remediations that have been created. The folder structure for the Git repository should be similiar to the following:

```shell
├── hier_config_dynamic_remediations
│ ├── __init__.py // necessary to discover the functions dynamically
│ ├── file_1.py
│ ├── file_2.py
│ └── file_3.py
```

Each dynamic remediation file should have a function called `remediation` that accepts a `remediation_config` argument. The `remediation_config` argument is the `hier_config.root.HConfig` object that is created when the difference between the backup and intended config is generated by the **Perform Configuration Compliance** job. Any changes should be made directly to that arugment.

For example, the following code would remove any sequence numbers from a Cisco IOS ACL config block:

```python
def remediation(remediation_config):
lineage = remediation_config.get_child("startswith", "ip access-list")
for line in lineage.all_children():
line_items = line.text.split()
index = 1 if line.text.startswith("no") else 0
line_items.remove(line_items[index])
lineage.del_child_by_text(line.text)
lineage.add_child(" ".join(line_items))
```

### Custom Config Remediation Type

When a Network Operating System delivers configuration data in a format that is not CLI/Hierarchical, we can still perform remediation by using the Custom Remediation options. Custom Remediation is defined within a Python function that takes as input a Configuration Compliance object and returns a Remediation Field.
Expand Down
2 changes: 2 additions & 0 deletions nautobot_golden_config/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ class RemediationTypeChoice(ChoiceSet):
"""Choiceset used by RemediationSetting."""

TYPE_HIERCONFIG = "hierconfig"
TYPE_DYNAMIC_HIERCONFIG = "dynamic_hierconfig"
TYPE_CUSTOM = "custom_remediation"

CHOICES = (
(TYPE_HIERCONFIG, "HIERCONFIG"),
(TYPE_DYNAMIC_HIERCONFIG, "DYNAMIC_HIERCONFIG"),
(TYPE_CUSTOM, "CUSTOM_REMEDIATION"),
)

Expand Down
38 changes: 38 additions & 0 deletions nautobot_golden_config/datasources.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,32 @@ def refresh_git_backup(repository_record, job_result, delete=False): # pylint:
)


def refresh_git_gc_dynamic_remediations(repository_record, job_result, delete=False): # pylint: disable=unused-argument
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at the code, but this part makes sense, I think that this is mostly what needs to be updated, more specifically there shouldn't be a reason to create a new model here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be possible to have a similar solution :

  • custom remediation method is provided via nautobot_config.py or packaged
  • custom remediation points to a dispatcher provided by git.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made some changes to remove the model and just load modules from the git repo when doing remediation; lmk what you think

"""Callback for gitrepository updates on Hier Config Dynamic Remediation repo."""
job_result.log(
"Successfully Pulled git repo test",
level_choice=LogLevelChoices.LOG_DEBUG,
)

dynamic_remediation_path = os.path.join(repository_record.filesystem_path, "hier_config_dynamic_remediations")
if not os.path.isdir(dynamic_remediation_path):
job_result.log(
f"Skipping sync for {dynamic_remediation_path} because directory doesn't exist.",
level_choice=LogLevelChoices.LOG_INFO,
)
return

for root, _, files in os.walk(dynamic_remediation_path):
for file_name in files:
if not file_name.endswith(".py") or "__init__" in file_name:
continue
# file_info.append({"root": root, "file_name": file_name})
job_result.log(
f"File {file_name} found in repository.",
level_choice=LogLevelChoices.LOG_INFO
)


def refresh_git_gc_properties(repository_record, job_result, delete=False): # pylint: disable=unused-argument
"""Callback for gitrepository updates on Git Configuration repo.

Expand Down Expand Up @@ -244,6 +270,18 @@ def update_git_gc_properties(golden_config_path, job_result, gc_config_item): #
),
)
)
if ENABLE_COMPLIANCE:
datasource_contents.append(
(
"extras.gitrepository",
DatasourceContent(
name="Hier Config Dynamic Remediations",
content_identifier="nautobot_golden_config.hierconfigdynamicremediations",
icon="mdi-file-code",
callback=refresh_git_gc_dynamic_remediations,
),
)
)

datasource_contents.append(
(
Expand Down
19 changes: 17 additions & 2 deletions nautobot_golden_config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import json
import logging
import pkgutil
import sys

from deepdiff import DeepDiff
from django.core.exceptions import ValidationError
Expand All @@ -11,7 +13,7 @@
from nautobot.core.models.generics import PrimaryModel
from nautobot.core.models.utils import serialize_object, serialize_object_v2
from nautobot.dcim.models import Device
from nautobot.extras.models import ObjectChange
from nautobot.extras.models import ObjectChange, GitRepository
from nautobot.extras.models.statuses import StatusField
from nautobot.extras.utils import extras_features
from netutils.config.compliance import feature_compliance
Expand Down Expand Up @@ -198,7 +200,19 @@ def _get_hierconfig_remediation(obj):

host.load_generated_config(obj.intended)
host.load_running_config(obj.actual)
host.remediation_config()
rem = host.remediation_config()

if remediation_setting_obj.remediation_type == RemediationTypeChoice.TYPE_DYNAMIC_HIERCONFIG:
repos = GitRepository.objects.filter(provided_contents__contains="nautobot_golden_config.hierconfigdynamicremediations")
for repo in repos:
for importer, discovered_module_name, _ in pkgutil.iter_modules(
[f"{repo.filesystem_path}/hier_config_dynamic_remediations"]
):
if "__init__" in discovered_module_name:
continue
module = importer.find_module(discovered_module_name).load_module(discovered_module_name)
module.remediation(rem)

remediation_config = host.remediation_config_filtered_text(include_tags={}, exclude_tags={})

return remediation_config
Expand All @@ -210,6 +224,7 @@ def _get_hierconfig_remediation(obj):
ComplianceRuleConfigTypeChoice.TYPE_JSON: _get_json_compliance,
ComplianceRuleConfigTypeChoice.TYPE_XML: _get_xml_compliance,
RemediationTypeChoice.TYPE_HIERCONFIG: _get_hierconfig_remediation,
RemediationTypeChoice.TYPE_DYNAMIC_HIERCONFIG: _get_hierconfig_remediation,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not tracking to me, we are pointing it to the same function of _get_hierconfig_remediation(), which would indicated to me, it's the same feature with a different name, but I think I am missing something, can you elaborate on this one?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i added that as an option so that if your environment had dynamic functions loaded via git and for a particular remediation setting you didn't need or want to use any dynamic functions at all, it bypassed that loop over the dynamic functions entirely

}
# The below conditionally add the custom provided compliance type
for custom_function, custom_type in CUSTOM_FUNCTIONS.items():
Expand Down