Manages the configuration on network devices.

    By default this state will commit the changes on the device. If there are no changes required, it does not commit
    and the field ``already_configured`` from the output dictionary will be set as ``True`` to notify that.

    To avoid committing the configuration, set the argument ``test`` to ``True`` (or via the CLI argument ``test=True``)
    and will discard (dry run).

    To preserve the changes, set ``commit`` to ``False`` (either as CLI argument, either as state parameter).
    However, this is recommended to be used only in exceptional cases when there are applied few consecutive states
    and/or configuration changes. Otherwise the user might forget that the config DB is locked and the candidate config
    buffer is not cleared/merged in the running config.

    To replace the config, set ``replace`` to ``True``. This option is recommended to be used with caution!

    template_name
        Identifies path to the template source. The template can be either stored on the local machine,
        either remotely.
        The recommended location is under the ``file_roots`` as specified in the master config file.
        For example, let's suppose the ``file_roots`` is configured as:

        .. code-block:: yaml

            file_roots:
              base:
                 - /etc/salt/states

        Placing the template under ``/etc/salt/states/templates/example.jinja``, it can be used as
        ``salt://templates/example.jinja``.
        Alternatively, for local files, the user can specify the absolute path.
        If remotely, the source can be retrieved via ``http``, ``https`` or ``ftp``.

        Examples:

        - ``salt://my_template.jinja``
        - ``/absolute/path/to/my_template.jinja``
        - ``http://example.com/template.cheetah``
        - ``https:/example.com/template.mako``
        - ``ftp://example.com/template.py``

        .. versionchanged:: 2019.2.0
            This argument can now support a list of templates to be rendered.
            The resulting configuration text is loaded at once, as a single
            configuration chunk.

    template_source: None
        Inline config template to be rendered and loaded on the device.

    template_hash: None
        Hash of the template file. Format: ``{hash_type: 'md5', 'hsum': <md5sum>}``

    template_hash_name: None
        When ``template_hash`` refers to a remote file, this specifies the filename to look for in that file.

    saltenv: base
        Specifies the template environment. This will influence the relative imports inside the templates.

    template_engine: jinja
        The following templates engines are supported:

        - :mod:`cheetah<salt.renderers.cheetah>`
        - :mod:`genshi<salt.renderers.genshi>`
        - :mod:`jinja<salt.renderers.jinja>`
        - :mod:`mako<salt.renderers.mako>`
        - :mod:`py<salt.renderers.py>`
        - :mod:`wempy<salt.renderers.wempy>`

    skip_verify: False
        If ``True``, hash verification of remote file sources (``http://``, ``https://``, ``ftp://``) will be skipped,
        and the ``source_hash`` argument will be ignored.

        .. versionchanged:: 2017.7.1

    test: False
        Dry run? If set to ``True``, will apply the config, discard and return the changes. Default: ``False``
        (will commit the changes on the device).

    commit: True
        Commit? Default: ``True``.

    debug: False
        Debug mode. Will insert a new key under the output dictionary, as ``loaded_config`` containing the raw
        result after the template was rendered.

        .. note::
            This argument cannot be used directly on the command line. Instead,
            it can be passed through the ``pillar`` variable when executing
            either of the :py:func:`state.sls <salt.modules.state.sls>` or
            :py:func:`state.apply <salt.modules.state.apply>` (see below for an
            example).

    commit_in: ``None``
        Commit the changes in a specific number of minutes / hours. Example of
        accepted formats: ``5`` (commit in 5 minutes), ``2m`` (commit in 2
        minutes), ``1h`` (commit the changes in 1 hour)`, ``5h30m`` (commit
        the changes in 5 hours and 30 minutes).

        .. note::
            This feature works on any platforms, as it does not rely on the
            native features of the network operating system.

        .. note::
            After the command is executed and the ``diff`` is not satisfactory,
            or for any other reasons you have to discard the commit, you are
            able to do so using the
            :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>`
            execution function, using the commit ID returned by this function.

        .. warning::
            Using this feature, Salt will load the exact configuration you
            expect, however the diff may change in time (i.e., if an user
            applies a manual configuration change, or a different process or
            command changes the configuration in the meanwhile).

        .. versionadded:: 2019.2.0

    commit_at: ``None``
        Commit the changes at a specific time. Example of accepted formats:
        ``1am`` (will commit the changes at the next 1AM), ``13:20`` (will
        commit at 13:20), ``1:20am``, etc.

        .. note::
            This feature works on any platforms, as it does not rely on the
            native features of the network operating system.

        .. note::
            After the command is executed and the ``diff`` is not satisfactory,
            or for any other reasons you have to discard the commit, you are
            able to do so using the
            :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>`
            execution function, using the commit ID returned by this function.

        .. warning::
            Using this feature, Salt will load the exact configuration you
            expect, however the diff may change in time (i.e., if an user
            applies a manual configuration change, or a different process or
            command changes the configuration in the meanwhile).

        .. versionadded:: 2019.2.0

    revert_in: ``None``
        Commit and revert the changes in a specific number of minutes / hours.
        Example of accepted formats: ``5`` (revert in 5 minutes), ``2m`` (revert
        in 2 minutes), ``1h`` (revert the changes in 1 hour)`, ``5h30m`` (revert
        the changes in 5 hours and 30 minutes).

        .. note::
            To confirm the commit, and prevent reverting the changes, you will
            have to execute the
            :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>`
            function, using the commit ID returned by this function.

        .. warning::
            This works on any platform, regardless if they have or don't have
            native capabilities to confirming a commit. However, please be
            *very* cautious when using this feature: on Junos (as it is the only
            NAPALM core platform supporting this natively) it executes a commit
            confirmed as you would do from the command line.
            All the other platforms don't have this capability natively,
            therefore the revert is done via Salt. That means, your device needs
            to be reachable at the moment when Salt will attempt to revert your
            changes. Be cautious when pushing configuration changes that would
            prevent you reach the device.

            Similarly, if an user or a different process apply other
            configuration changes in the meanwhile (between the moment you
            commit and till the changes are reverted), these changes would be
            equally reverted, as Salt cannot be aware of them.

        .. versionadded:: 2019.2.0

    revert_at: ``None``
        Commit and revert the changes at a specific time. Example of accepted
        formats: ``1am`` (will commit and revert the changes at the next 1AM),
        ``13:20`` (will commit and revert at 13:20), ``1:20am``, etc.

        .. note::
            To confirm the commit, and prevent reverting the changes, you will
            have to execute the
            :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>`
            function, using the commit ID returned by this function.

        .. warning::
            This works on any platform, regardless if they have or don't have
            native capabilities to confirming a commit. However, please be
            *very* cautious when using this feature: on Junos (as it is the only
            NAPALM core platform supporting this natively) it executes a commit
            confirmed as you would do from the command line.
            All the other platforms don't have this capability natively,
            therefore the revert is done via Salt. That means, your device needs
            to be reachable at the moment when Salt will attempt to revert your
            changes. Be cautious when pushing configuration changes that would
            prevent you reach the device.

            Similarly, if an user or a different process apply other
            configuration changes in the meanwhile (between the moment you
            commit and till the changes are reverted), these changes would be
            equally reverted, as Salt cannot be aware of them.

        .. versionadded:: 2019.2.0

    replace: False
        Load and replace the configuration. Default: ``False`` (will apply load merge).

    context: None
        Overrides default context variables passed to the template.

        .. versionadded:: 2019.2.0

    defaults: None
        Default variables/context passed to the template.

    template_vars
        Dictionary with the arguments/context to be used when the template is rendered. Do not explicitly specify this
        argument. This represents any other variable that will be sent to the template rendering system. Please
        see an example below! In both ``ntp_peers_example_using_pillar`` and ``ntp_peers_example``, ``peers`` is sent as
        template variable.

        .. note::
            It is more recommended to use the ``context`` argument instead, to
            avoid any conflicts with other arguments.

    SLS Example (e.g.: under salt://router/config.sls) :

    .. code-block:: yaml

        whole_config_example:
            netconfig.managed:
                - template_name: salt://path/to/complete_config.jinja
                - debug: True
                - replace: True
        bgp_config_example:
            netconfig.managed:
                - template_name: /absolute/path/to/bgp_neighbors.mako
                - template_engine: mako
        prefix_lists_example:
            netconfig.managed:
                - template_name: prefix_lists.cheetah
                - debug: True
                - template_engine: cheetah
        ntp_peers_example:
            netconfig.managed:
                - template_name: http://bit.ly/2gKOj20
                - skip_verify: False
                - debug: True
                - peers:
                    - 192.168.0.1
                    - 192.168.0.1
        ntp_peers_example_using_pillar:
            netconfig.managed:
                - template_name: http://bit.ly/2gKOj20
                - peers: {{ pillar.get('ntp.peers', []) }}

    Multi template example:

    .. code-block:: yaml

        hostname_and_ntp:
          netconfig.managed:
            - template_name:
                - https://bit.ly/2OhSgqP
                - https://bit.ly/2M6C4Lx
                - https://bit.ly/2OIWVTs
            - debug: true
            - context:
                hostname: {{ opts.id }}
                servers:
                  - 172.17.17.1
                  - 172.17.17.2
                peers:
                  - 192.168.0.1
                  - 192.168.0.2

    Usage examples:

    .. code-block:: bash

        $ sudo salt 'juniper.device' state.sls router.config test=True

        $ sudo salt -N all-routers state.sls router.config pillar="{'debug': True}"

    ``router.config`` depends on the location of the SLS file (see above). Running this command, will be executed all
    five steps from above. These examples above are not meant to be used in a production environment, their sole purpose
    is to provide usage examples.

    Output example:

    .. code-block:: bash

        $ sudo salt 'juniper.device' state.sls router.config test=True
        juniper.device:
        ----------
                  ID: ntp_peers_example_using_pillar
            Function: netconfig.managed
              Result: None
             Comment: Testing mode: Configuration discarded.
             Started: 12:01:40.744535
            Duration: 8755.788 ms
             Changes:
                      ----------
                      diff:
                          [edit system ntp]
                               peer 192.168.0.1 { ... }
                          +    peer 172.17.17.1;
                          +    peer 172.17.17.3;

        Summary for juniper.device
        ------------
        Succeeded: 1 (unchanged=1, changed=1)
        Failed:    0
        ------------
        Total states run:     1
        Total run time:   8.756 s

    Raw output example (useful when the output is reused in other states/execution modules):

    .. code-block:: bash

        $ sudo salt --out=pprint 'juniper.device' state.sls router.config test=True debug=True

    .. code-block:: python

        {
            'juniper.device': {
                'netconfig_|-ntp_peers_example_using_pillar_|-ntp_peers_example_using_pillar_|-managed': {
                    '__id__': 'ntp_peers_example_using_pillar',
                    '__run_num__': 0,
                    'already_configured': False,
                    'changes': {
                        'diff': '[edit system ntp]   peer 192.168.0.1 { ... }+   peer 172.17.17.1;+   peer 172.17.17.3;'
                    },
                    'comment': 'Testing mode: Configuration discarded.',
                    'duration': 7400.759,
                    'loaded_config': 'system {  ntp {  peer 172.17.17.1;  peer 172.17.17.3; } }',
                    'name': 'ntp_peers_example_using_pillar',
                    'result': None,
                    'start_time': '12:09:09.811445'
                }
            }
        }

def managed(name,
            template_name=None,
            template_source=None,
            template_hash=None,
            template_hash_name=None,
            saltenv='base',
            template_engine='jinja',
            skip_verify=False,
            context=None,
            defaults=None,
            test=False,
            commit=True,
            debug=False,
            replace=False,
            commit_in=None,
            commit_at=None,
            revert_in=None,
            revert_at=None,
            **template_vars):
    '''
    Manages the configuration on network devices.

    By default this state will commit the changes on the device. If there are no changes required, it does not commit
    and the field ``already_configured`` from the output dictionary will be set as ``True`` to notify that.

    To avoid committing the configuration, set the argument ``test`` to ``True`` (or via the CLI argument ``test=True``)
    and will discard (dry run).

    To preserve the changes, set ``commit`` to ``False`` (either as CLI argument, either as state parameter).
    However, this is recommended to be used only in exceptional cases when there are applied few consecutive states
    and/or configuration changes. Otherwise the user might forget that the config DB is locked and the candidate config
    buffer is not cleared/merged in the running config.

    To replace the config, set ``replace`` to ``True``. This option is recommended to be used with caution!

    template_name
        Identifies path to the template source. The template can be either stored on the local machine,
        either remotely.
        The recommended location is under the ``file_roots`` as specified in the master config file.
        For example, let's suppose the ``file_roots`` is configured as:

        .. code-block:: yaml

            file_roots:
              base:
                 - /etc/salt/states

        Placing the template under ``/etc/salt/states/templates/example.jinja``, it can be used as
        ``salt://templates/example.jinja``.
        Alternatively, for local files, the user can specify the absolute path.
        If remotely, the source can be retrieved via ``http``, ``https`` or ``ftp``.

        Examples:

        - ``salt://my_template.jinja``
        - ``/absolute/path/to/my_template.jinja``
        - ``http://example.com/template.cheetah``
        - ``https:/example.com/template.mako``
        - ``ftp://example.com/template.py``

        .. versionchanged:: 2019.2.0
            This argument can now support a list of templates to be rendered.
            The resulting configuration text is loaded at once, as a single
            configuration chunk.

    template_source: None
        Inline config template to be rendered and loaded on the device.

    template_hash: None
        Hash of the template file. Format: ``{hash_type: 'md5', 'hsum': <md5sum>}``

    template_hash_name: None
        When ``template_hash`` refers to a remote file, this specifies the filename to look for in that file.

    saltenv: base
        Specifies the template environment. This will influence the relative imports inside the templates.

    template_engine: jinja
        The following templates engines are supported:

        - :mod:`cheetah<salt.renderers.cheetah>`
        - :mod:`genshi<salt.renderers.genshi>`
        - :mod:`jinja<salt.renderers.jinja>`
        - :mod:`mako<salt.renderers.mako>`
        - :mod:`py<salt.renderers.py>`
        - :mod:`wempy<salt.renderers.wempy>`

    skip_verify: False
        If ``True``, hash verification of remote file sources (``http://``, ``https://``, ``ftp://``) will be skipped,
        and the ``source_hash`` argument will be ignored.

        .. versionchanged:: 2017.7.1

    test: False
        Dry run? If set to ``True``, will apply the config, discard and return the changes. Default: ``False``
        (will commit the changes on the device).

    commit: True
        Commit? Default: ``True``.

    debug: False
        Debug mode. Will insert a new key under the output dictionary, as ``loaded_config`` containing the raw
        result after the template was rendered.

        .. note::
            This argument cannot be used directly on the command line. Instead,
            it can be passed through the ``pillar`` variable when executing
            either of the :py:func:`state.sls <salt.modules.state.sls>` or
            :py:func:`state.apply <salt.modules.state.apply>` (see below for an
            example).

    commit_in: ``None``
        Commit the changes in a specific number of minutes / hours. Example of
        accepted formats: ``5`` (commit in 5 minutes), ``2m`` (commit in 2
        minutes), ``1h`` (commit the changes in 1 hour)`, ``5h30m`` (commit
        the changes in 5 hours and 30 minutes).

        .. note::
            This feature works on any platforms, as it does not rely on the
            native features of the network operating system.

        .. note::
            After the command is executed and the ``diff`` is not satisfactory,
            or for any other reasons you have to discard the commit, you are
            able to do so using the
            :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>`
            execution function, using the commit ID returned by this function.

        .. warning::
            Using this feature, Salt will load the exact configuration you
            expect, however the diff may change in time (i.e., if an user
            applies a manual configuration change, or a different process or
            command changes the configuration in the meanwhile).

        .. versionadded:: 2019.2.0

    commit_at: ``None``
        Commit the changes at a specific time. Example of accepted formats:
        ``1am`` (will commit the changes at the next 1AM), ``13:20`` (will
        commit at 13:20), ``1:20am``, etc.

        .. note::
            This feature works on any platforms, as it does not rely on the
            native features of the network operating system.

        .. note::
            After the command is executed and the ``diff`` is not satisfactory,
            or for any other reasons you have to discard the commit, you are
            able to do so using the
            :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>`
            execution function, using the commit ID returned by this function.

        .. warning::
            Using this feature, Salt will load the exact configuration you
            expect, however the diff may change in time (i.e., if an user
            applies a manual configuration change, or a different process or
            command changes the configuration in the meanwhile).

        .. versionadded:: 2019.2.0

    revert_in: ``None``
        Commit and revert the changes in a specific number of minutes / hours.
        Example of accepted formats: ``5`` (revert in 5 minutes), ``2m`` (revert
        in 2 minutes), ``1h`` (revert the changes in 1 hour)`, ``5h30m`` (revert
        the changes in 5 hours and 30 minutes).

        .. note::
            To confirm the commit, and prevent reverting the changes, you will
            have to execute the
            :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>`
            function, using the commit ID returned by this function.

        .. warning::
            This works on any platform, regardless if they have or don't have
            native capabilities to confirming a commit. However, please be
            *very* cautious when using this feature: on Junos (as it is the only
            NAPALM core platform supporting this natively) it executes a commit
            confirmed as you would do from the command line.
            All the other platforms don't have this capability natively,
            therefore the revert is done via Salt. That means, your device needs
            to be reachable at the moment when Salt will attempt to revert your
            changes. Be cautious when pushing configuration changes that would
            prevent you reach the device.

            Similarly, if an user or a different process apply other
            configuration changes in the meanwhile (between the moment you
            commit and till the changes are reverted), these changes would be
            equally reverted, as Salt cannot be aware of them.

        .. versionadded:: 2019.2.0

    revert_at: ``None``
        Commit and revert the changes at a specific time. Example of accepted
        formats: ``1am`` (will commit and revert the changes at the next 1AM),
        ``13:20`` (will commit and revert at 13:20), ``1:20am``, etc.

        .. note::
            To confirm the commit, and prevent reverting the changes, you will
            have to execute the
            :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>`
            function, using the commit ID returned by this function.

        .. warning::
            This works on any platform, regardless if they have or don't have
            native capabilities to confirming a commit. However, please be
            *very* cautious when using this feature: on Junos (as it is the only
            NAPALM core platform supporting this natively) it executes a commit
            confirmed as you would do from the command line.
            All the other platforms don't have this capability natively,
            therefore the revert is done via Salt. That means, your device needs
            to be reachable at the moment when Salt will attempt to revert your
            changes. Be cautious when pushing configuration changes that would
            prevent you reach the device.

            Similarly, if an user or a different process apply other
            configuration changes in the meanwhile (between the moment you
            commit and till the changes are reverted), these changes would be
            equally reverted, as Salt cannot be aware of them.

        .. versionadded:: 2019.2.0

    replace: False
        Load and replace the configuration. Default: ``False`` (will apply load merge).

    context: None
        Overrides default context variables passed to the template.

        .. versionadded:: 2019.2.0

    defaults: None
        Default variables/context passed to the template.

    template_vars
        Dictionary with the arguments/context to be used when the template is rendered. Do not explicitly specify this
        argument. This represents any other variable that will be sent to the template rendering system. Please
        see an example below! In both ``ntp_peers_example_using_pillar`` and ``ntp_peers_example``, ``peers`` is sent as
        template variable.

        .. note::
            It is more recommended to use the ``context`` argument instead, to
            avoid any conflicts with other arguments.

    SLS Example (e.g.: under salt://router/config.sls) :

    .. code-block:: yaml

        whole_config_example:
            netconfig.managed:
                - template_name: salt://path/to/complete_config.jinja
                - debug: True
                - replace: True
        bgp_config_example:
            netconfig.managed:
                - template_name: /absolute/path/to/bgp_neighbors.mako
                - template_engine: mako
        prefix_lists_example:
            netconfig.managed:
                - template_name: prefix_lists.cheetah
                - debug: True
                - template_engine: cheetah
        ntp_peers_example:
            netconfig.managed:
                - template_name: http://bit.ly/2gKOj20
                - skip_verify: False
                - debug: True
                - peers:
                    - 192.168.0.1
                    - 192.168.0.1
        ntp_peers_example_using_pillar:
            netconfig.managed:
                - template_name: http://bit.ly/2gKOj20
                - peers: {{ pillar.get('ntp.peers', []) }}

    Multi template example:

    .. code-block:: yaml

        hostname_and_ntp:
          netconfig.managed:
            - template_name:
                - https://bit.ly/2OhSgqP
                - https://bit.ly/2M6C4Lx
                - https://bit.ly/2OIWVTs
            - debug: true
            - context:
                hostname: {{ opts.id }}
                servers:
                  - 172.17.17.1
                  - 172.17.17.2
                peers:
                  - 192.168.0.1
                  - 192.168.0.2

    Usage examples:

    .. code-block:: bash

        $ sudo salt 'juniper.device' state.sls router.config test=True

        $ sudo salt -N all-routers state.sls router.config pillar="{'debug': True}"

    ``router.config`` depends on the location of the SLS file (see above). Running this command, will be executed all
    five steps from above. These examples above are not meant to be used in a production environment, their sole purpose
    is to provide usage examples.

    Output example:

    .. code-block:: bash

        $ sudo salt 'juniper.device' state.sls router.config test=True
        juniper.device:
        ----------
                  ID: ntp_peers_example_using_pillar
            Function: netconfig.managed
              Result: None
             Comment: Testing mode: Configuration discarded.
             Started: 12:01:40.744535
            Duration: 8755.788 ms
             Changes:
                      ----------
                      diff:
                          [edit system ntp]
                               peer 192.168.0.1 { ... }
                          +    peer 172.17.17.1;
                          +    peer 172.17.17.3;

        Summary for juniper.device
        ------------
        Succeeded: 1 (unchanged=1, changed=1)
        Failed:    0
        ------------
        Total states run:     1
        Total run time:   8.756 s

    Raw output example (useful when the output is reused in other states/execution modules):

    .. code-block:: bash

        $ sudo salt --out=pprint 'juniper.device' state.sls router.config test=True debug=True

    .. code-block:: python

        {
            'juniper.device': {
                'netconfig_|-ntp_peers_example_using_pillar_|-ntp_peers_example_using_pillar_|-managed': {
                    '__id__': 'ntp_peers_example_using_pillar',
                    '__run_num__': 0,
                    'already_configured': False,
                    'changes': {
                        'diff': '[edit system ntp]   peer 192.168.0.1 { ... }+   peer 172.17.17.1;+   peer 172.17.17.3;'
                    },
                    'comment': 'Testing mode: Configuration discarded.',
                    'duration': 7400.759,
                    'loaded_config': 'system {  ntp {  peer 172.17.17.1;  peer 172.17.17.3; } }',
                    'name': 'ntp_peers_example_using_pillar',
                    'result': None,
                    'start_time': '12:09:09.811445'
                }
            }
        }
    '''
    ret = salt.utils.napalm.default_ret(name)

    # the user can override the flags the equivalent CLI args
    # which have higher precedence
    test = __salt__['config.merge']('test', test)
    debug = __salt__['config.merge']('debug', debug)
    commit = __salt__['config.merge']('commit', commit)
    replace = __salt__['config.merge']('replace', replace)  # this might be a bit risky
    skip_verify = __salt__['config.merge']('skip_verify', skip_verify)
    commit_in = __salt__['config.merge']('commit_in', commit_in)
    commit_at = __salt__['config.merge']('commit_at', commit_at)
    revert_in = __salt__['config.merge']('revert_in', revert_in)
    revert_at = __salt__['config.merge']('revert_at', revert_at)

    config_update_ret = _update_config(template_name=template_name,
                                       template_source=template_source,
                                       template_hash=template_hash,
                                       template_hash_name=template_hash_name,
                                       saltenv=saltenv,
                                       template_engine=template_engine,
                                       skip_verify=skip_verify,
                                       context=context,
                                       defaults=defaults,
                                       test=test,
                                       commit=commit,
                                       commit_in=commit_in,
                                       commit_at=commit_at,
                                       revert_in=revert_in,
                                       revert_at=revert_at,
                                       debug=debug,
                                       replace=replace,
                                       **template_vars)

    return salt.utils.napalm.loaded_ret(ret, config_update_ret, test, debug)