Skip to content

Navigating Configuration Post-processing

Note

The current implementation only renders the configuration for pushing and does not update the configuration on the target devices.

The intended configuration job doesn't produce a final configuration artifact (see below for reasons). The intended configuration represents the "intended" running configuration, as it generates what is expected to be in the final running configuration. While this approach works well for the "compliance" feature, it is less effective for creating a configuration artifact that is ready to be pushed to devices.

Challenges when using the running configuration as the intended configuration:

  • Since the intended configuration is stored in both the database and an external Git repository, it should not contain any secrets.
  • The format of the running configuration may differ from the configuration that needs to be pushed. Examples include:
    • SNMPv3 configurations, which do not appear in the running configuration
    • VTP configurations that are entirely absent from the running configuration
    • Implicit configurations, such as "no shutdown" commands on interfaces
  • Configurations necessary to achieve the intended state may need to be ordered carefully to prevent outages.

As the Golden Config application evolves into a comprehensive configuration management solution, it requires an advanced feature to generate a configuration artifact that is in the final format expected by your device, based on the intended configuration.

This is achieved through the get_config_postprocessing() function defined in nautobot_golden_config.utilities.config_postprocessing. This method processes the configurations generated by the Golden Config intended configuration feature, along with the HTTP request. It returns the intended configuration that is ready to be pushed.

From a user perspective, you can retrieve this configuration using two methods:

  • UI: In the Device detail view, if the feature is enabled, a new row appears under "Configuration Types." Clicking the icon renders the new configuration on the fly (synchronously). See the figure below for reference.
  • REST API: You can request the processed intended configuration at the path /api/plugins/golden-config/config-postprocessing/{device_id}. The return payload will contain a "config" key with the rendered configuration.

Configuration Postprocessing

Customizing Configuration Processing

There are two ways to customize the default behavior of the get_config_postprocessing method:

  • postprocessing_callables: A list of available methods for processing the intended configuration. It includes some default methods, such as render_secrets, but can be extended via configuration options (see the next section). These methods are defined using a dotted string format that Django imports. For example, render_secrets is defined as "nautobot_golden_config.utilities.config_postprocessing.render_secrets".
  • postprocessing_subscribed: A list of method names (strings) that define the order in which methods are executed. The methods must exist in the postprocessing_callables list. This list can be customized through configuration options and could eventually accept HTTP query parameters for further customization.

Default Processors

Render Secrets

The render_secrets function performs an additional Jinja rendering on the intended configuration, providing custom Jinja filters:

  • get_secret_by_secret_group_name: As the name implies, this filter returns the value of a secret group for a given secret type based on its name.

Note

Standard Django or Netutils filters are not available in this Jinja environment. Only encrypt_<vendor>_type5 and encrypt_<vendor>_type7 filters can be used in conjunction with the get_secret filters.

Since this rendering occurs after the initial generation of the intended configuration, the {% raw %} Jinja syntax must be used to prevent premature processing.

  1. For example, an original template might look like this: {% raw %}ppp pap sent-username {{ secrets_group["name"] | get_secret_by_secret_group_name("username") }}{% endraw %}
  2. It produces an intended configuration like this: ppp pap sent-username {{ secrets_group["name"] | get_secret_by_secret_group_name("username") }}
  3. After applying render_secrets, it becomes: ppp pap sent-username my_username.

Note that the get_secret filters accept arguments. In the example, the secret_group name is passed along with the type of secret. You can customize the signature for additional options.

Note

To render secrets, the user requesting the configuration via UI or API must have read permissions for Secrets Groups, Golden Config, and the specific Device object.

Render Secrets Example

Here is an example of rendering secrets for a Device, using the default Secrets Group ForeignKey (FK) and custom relationships, in this case at the Location level.

GraphQL Query
query ($device_id: ID!) {
  device(id: $device_id) {
    secrets_group {
      name
    }
    location {
      rel_my_secret_relationship_for_location {
        name
      }
    }
  }
}
Jinja Template

Using the default secrets_group FK in Device:

{% raw %}{{ secrets_group["name"] | get_secret_by_secret_group_name("password") | default('no password') }}{% endraw %}

Using the custom relationship at the Location level:

{% raw %}{{ location["rel_my_secret_relationship_for_location"][0]["name"] | get_secret_by_secret_group_name("password") | default('no password') }}{% endraw %}

This will render the secret of type "password" for the corresponding SecretGroup.

Managing Errors

The rendering process may encounter issues, which are managed and properly explained to guide corrective actions:

Found an error rendering the configuration to push: Jinja encountered and UndefinedError: 'None' has no attribute 'name', check the template for missing variable definitions.