This file is indexed.

/usr/lib/python3/dist-packages/pyeapi/client.py is in python3-pyeapi 0.8.1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
#
# Copyright (c) 2014, Arista Networks, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
#   Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
#   Neither the name of Arista Networks nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
"""Python Client for eAPI

This module provides the client for eAPI.  It provides the primary functions
for building applications that work with Arista EOS eAPI-enabled nodes.  The
first function is to provide a client for sending and receiving eAPI
request and response objects on a per node basis.  The second function
provides a library for building API enabled data models for configuring
EOS nodes.

This library allows for creating connections to EOS eAPI enabled nodes using
the connect or connect_to function.  Both functions will return an instance
of a Node object that can be used to send and receive eAPI commands.  The
Node object can autoload API modules for a structured object oriented
approach to configuring the EOS node with native Python objects.

Example:

    >>> import pyeapi
    >>> conn = pyeapi.connect(host='10.1.1.1', transport='http')
    >>> conn.execute(['show verson'])
    {u'jsonrpc': u'2.0', u'result': [{u'memTotal': 2028008, u'version':
    u'4.14.5F', u'internalVersion': u'4.14.5F-2209869.4145F', u'serialNumber':
    u'', u'systemMacAddress': u'00:0c:29:f5:d2:7d', u'bootupTimestamp':
    1421765066.11, u'memFree': 213212, u'modelName': u'vEOS', u'architecture':
    u'i386', u'internalBuildId': u'f590eed4-1e66-43c6-8943-cee0390fbafe',
    u'hardwareRevision': u''}], u'id': u'4312565648'}

    >>> node = pyeapi.connect_to('veos01')
    >>> node.enable('show version')
    {u'jsonrpc': u'2.0', u'result': [{u'memTotal': 2028008, u'version':
    u'4.14.5F', u'internalVersion': u'4.14.5F-2209869.4145F', u'serialNumber':
    u'', u'systemMacAddress': u'00:0c:29:f5:d2:7d', u'bootupTimestamp':
    1421765066.11, u'memFree': 213212, u'modelName': u'vEOS', u'architecture':
    u'i386', u'internalBuildId': u'f590eed4-1e66-43c6-8943-cee0390fbafe',
    u'hardwareRevision': u''}], u'id': u'4312565648'}

Additionally the node object can automatically load API modules to work
with the resources in the configuration.  The API autoloader supports
automatic loading of modules in pyeapi.api as well as provides the ability
to build custom API modules to be loaded from a different namespace.

Example:

    >>> import pyeapi
    >>> node = pyeapi.connect_to('veos01')
    >>> node.api('vlans').get(1)
    {'state': 'active', 'name': 'default', 'vlan_id': 1, 'trunk_groups': []}

The API autoloader loads API modules by their filename.

The following objects are provide in this module for creating clients to
interface with eAPI.

Node -- Creates an instance of a node object that represents a single EOS
device.  Each EOS device to be managed should have a Node instance

Config -- A subclass of ConfigParser.SafeConfigParser that handles the
configuration file.  The configuration file is an INI style file that
contains the settings for nodes used by the connect_to function.

"""
import os
import re

try:
    # Try Python 3.x import first
    # Note: SafeConfigParser is deprecated and replaced by ConfigParser
    from configparser import ConfigParser as SafeConfigParser
    from configparser import Error as SafeConfigParserError
except ImportError:
    # Use Python 2.7 import as a fallback
    from ConfigParser import SafeConfigParser
    from ConfigParser import Error as SafeConfigParserError

from pyeapi.utils import load_module, make_iterable, debug

from pyeapi.eapilib import HttpEapiConnection, HttpsEapiConnection
from pyeapi.eapilib import SocketEapiConnection, HttpLocalEapiConnection
from pyeapi.eapilib import CommandError

CONFIG_SEARCH_PATH = ['~/.eapi.conf', '/mnt/flash/eapi.conf']

TRANSPORTS = {
    'socket': SocketEapiConnection,
    'http_local': HttpLocalEapiConnection,
    'http': HttpEapiConnection,
    'https': HttpsEapiConnection
}

DEFAULT_TRANSPORT = 'https'


class Config(SafeConfigParser):
    """Conifguration instance for managing the eapi.conf file.

    This class provides an instance for handling the configuration file.  It
    should normally need to be instantiated.  A single config object is
    instantiated by the module for working with the config.

    Attributes:
        filename (str): The full path to the loaded filename

    Args:
        filename(str): The full path to the filename to be loaded when the
            object is instantiated.

    """
    def __init__(self, filename=None):
        SafeConfigParser.__init__(self)

        self.filename = filename
        self.tags = dict()

        self.autoload()

    @property
    def connections(self):
        """
        Returns all of the loaded connections names as a list
        """
        conn = lambda x: str(x).replace('connection:', '')
        return [conn(name) for name in self.sections()]

    def autoload(self):
        """ Loads the eapi.conf file

        This method will use the module variable CONFIG_SEARCH_PATH to
        attempt to locate a valid eapi.conf file if a filename is not already
        configured.   This method will load the first eapi.conf file it
        finds and then return.

        The CONFIG_SEARCH_PATH can be overridden using an environment variable
        by setting EAPI_CONF.

        """
        path = list(CONFIG_SEARCH_PATH)
        if 'EAPI_CONF' in os.environ:
            path = os.environ['EAPI_CONF']
        elif self.filename:
            path = self.filename

        path = make_iterable(path)

        for filename in path:
            filename = os.path.expanduser(filename)
            if os.path.exists(filename):
                self.filename = filename
                return self.read(filename)

        self._add_default_connection()

    def read(self, filename):
        """Reads the file specified by filename

        This method will load the eapi.conf file specified by filename into
        the instance object.  It will also add the default connection localhost
        if it was not defined in the eapi.conf file

        Args:
            filename (str): The full path to the file to load
        """

        try:
            SafeConfigParser.read(self, filename)
        except SafeConfigParserError as exc:
            # Ignore file and syslog a message on SafeConfigParser errors
            msg = ("%s: parsing error in eapi conf file: %s" %
                   (type(exc).__name__, filename))
            debug(msg)

        self._add_default_connection()

        for name in self.sections():
            if name.startswith('connection:') and \
               'host' not in dict(self.items(name)):

                self.set(name, 'host', name.split(':')[1])
        self.generate_tags()

    def _add_default_connection(self):
        """Checks the loaded config and adds the localhost profile if needed

        This method wil load the connection:localhost profile into the client
        configuration if it is not already present.
        """
        if not self.get_connection('localhost'):
            self.add_connection('localhost', transport='socket')

    def generate_tags(self):
        """ Generates the tags with collection with hosts
        """
        self.tags = dict()
        for section in self.sections():
            if self.has_option(section, 'tags'):
                tags = self.get(section, 'tags')
                for tag in [str(t).strip() for t in tags.split(',')]:
                    if tag not in self.tags:
                        self.tags[tag] = list()
                    self.tags[tag].append(section.split(':')[1])

    def load(self, filename):
        """Loads the file specified by filename

        This method works in conjunction with the autoload method to load the
        file specified by filename.

        Args:
            filename (str): The full path to the file to be loaded
        """
        self.filename = filename
        self.reload()

    def reload(self):
        """Reloades the configuration

        This method will reload the configuration instance using the last
        known filename.  Note this method will initially clear the
        configuration and reload all entries.

        """
        for section in self.sections():
            self.remove_section(section)
        self.autoload()

    def get_connection(self, name):
        """Returns the properties for a connection name

        This method will return the settings for the configuration specified
        by name.  Note that the name argument should only be the name.

        For instance, give the following eapi.conf file

        .. code-block:: ini

            [connection:veos01]
            transport: http

        The name to use to retrieve the configuration would be veos01

            >>> pyeapi.client.config.get_connection('veos01')

        Args:
            name (str): The name of the connection to return

        Returns:
            A Python dictionary object of key/value pairs that represent
                the node configuration.  If the name provided in the argument
                is not found, then None is returned.
        """
        name = 'connection:{}'.format(name)
        if not self.has_section(name):
            return None
        return dict(self.items(name))

    def add_connection(self, name, **kwargs):
        """Adds a connection to the configuration

        This method will add a connection to the configuration.  The connection
        added is only available for the lifetime of the object and is not
        persisted.

        Note:
            If a call is made to load() or reload(), any connections added
            with this method must be re-added to the config instance

        Args:
            name (str): The name of the connection to add to the config.  The
                name provided will automatically be prepended with the string
                connection:
            **kwargs (dict); The set of properties used to provide the node
                configuration

        """
        name = 'connection:{}'.format(name)
        self.add_section(name)
        for key, value in list(kwargs.items()):
            self.set(name, key, value)
        self.generate_tags()

# TODO: This is a global variable (in the module) - to review the impact on
# having a shared state for the config file.


config = Config()


def load_config(filename):
    """Function method that loads a conf file

    This function will load the file specified by filename into the config
    instance.   Its a convenience function that calls load on the config
    instance

    Args:
        filename (str): The full path to the filename to load
    """
    return config.load(filename)

def config_for(name):
    """ Function to get settings for named config

    This function will return the settings for a specific connection as
    specified by name.  Its a convenience function that calls get_connection
    on the global config instance

    Args:
        name (str): The name of the connection to return.  The connection
            name is specified as the string right of the : in the INI file

    Returns:
        A Python dictionary object of key/value pairs that represent the
            nodes configuration settings from the config instance
    """
    return config.get_connection(name)

def hosts_for_tag(tag):
    """ Returns the hosts assocated with the specified tag

    This function will return the hosts assoicated with the tag specified
    in the argument.  It will return an array of connecition names.

    Args:
        tag (str): The name of the tag to retrieve the list of hosts for

    Returns:
        list: A Python list object that includes the list of hosts assoicated
            with the specified tag.

        None: If the specified tag does not exist, then None is returned.
    """
    return config.tags.get(tag)

def make_connection(transport, **kwargs):
    """ Creates a connection instance based on the transport

    This function creates the EapiConnection object based on the desired
    transport.  It looks up the transport class in the TRANSPORTS global
    dictionary.

    Args:
        transport (string): The transport to use to create the instance.
        **kwargs: Arbitrary keyword arguments.

    Returns:
        An instance of a connection object based on the transport

    Raises:
        TypeError: A TypeError is raised if the transport keyword is not
            found in the list (keys) of available transports.
    """
    if transport not in TRANSPORTS:
        raise TypeError('invalid transport specified')
    klass = TRANSPORTS[transport]
    return klass(**kwargs)


def connect(transport=None, host='localhost', username='admin',
            password='', port=None, timeout=60, return_node=False, **kwargs):
    """ Creates a connection using the supplied settings

    This function will create a connection to an Arista EOS node using
    the arguments.  All arguments are optional with default values.

    Args:
        transport (str): Specifies the type of connection transport to use.
            Valid values for the connection are socket, http_local, http, and
            https.  The default value is specified in DEFAULT_TRANSPORT
        host (str): The IP addres or DNS host name of the connection device.
            The default value is 'localhost'
        username (str): The username to pass to the device to authenticate
            the eAPI connection.   The default value is 'admin'
        password (str): The password to pass to the device to authenticate
            the eAPI connection.  The default value is ''
        port (int): The TCP port of the endpoint for the eAPI connection.  If
            this keyword is not specified, the default value is automatically
            determined by the transport type. (http=80, https=443)
        return_node (bool): Returns a Node object if True, otherwise
            returns an EapiConnection object.


    Returns:
        An instance of an EapiConnection object for the specified transport.

    """
    transport = transport or DEFAULT_TRANSPORT
    connection = make_connection(transport, host=host, username=username,
                                 password=password, port=port, timeout=timeout)
    if return_node:
        return Node(connection, transport=transport, host=host,
                    username=username, password=password, port=port, **kwargs)
    return connection


class Node(object):
    """Represents a single device for sending and receiving eAPI messages

    The Node object provides an instance for communicating with Arista EOS
    devices.  The Node object provides easy to use methods for sending both
    enable and config commands to the device using a specific transport.  This
    object forms the base for communicating with devices.

    Attributes:
        connection (EapiConnection): The connection property represents the
            underlying transport used by the Node object to communicate
            with the device using eAPI.
        running_config (str): The running-config from the device.  This
            property is lazily loaded and refreshed over the life cycle of
            the instance.
        startup_config (str): The startup-config from the device.  This
            property is lazily loaded and refreshed over the life cycle of
            the instance.
        autorefresh (bool): If True, the running-config and startup-config are
            refreshed on config events.  If False, then the config properties
            must be manually refreshed.
        settings (dict): Provides access to the settings used to create the
            Node instance.

    Args:
        connection (EapiConnection): An instance of EapiConnection used as the
            transport for sending and receiving eAPI requests and responses.
        **kwargs: An arbitrary list of keyword arguments
    """
    def __init__(self, connection, **kwargs):
        self._connection = connection
        self._running_config = None
        self._startup_config = None
        self._version = None
        self._version_number = None
        self._model = None

        self._enablepwd = kwargs.get('enablepwd')
        self.autorefresh = kwargs.get('autorefresh', True)
        self.settings = kwargs

    def __str__(self):
        return 'Node(connection=%s)' % str(self._connection)

    def __repr__(self):
        return 'Node(connection=%s)' % repr(self._connection)

    @property
    def connection(self):
        return self._connection

    @property
    def running_config(self):
        if self._running_config is not None:
            return self._running_config
        self._running_config = self.get_config(params='all',
                                               as_string=True)
        return self._running_config

    @property
    def startup_config(self):
        if self._startup_config is not None:
            return self._startup_config
        self._startup_config = self.get_config('startup-config',
                                               as_string=True)
        return self._startup_config

    @property
    def version(self):
        if self._version:
            return self._version
        self._get_version_properties()
        return self._version

    @property
    def version_number(self):
        if self._version_number:
            return self._version_number
        self._get_version_properties()
        return self._version_number

    @property
    def model(self):
        if self._model:
            return self._model
        self._get_version_properties()
        return self._model

    def _get_version_properties(self):
        """Parses version and model information out of 'show version' output
        and uses the output to populate class properties.
        """
        # Parse out version info
        output = self.enable('show version')
        self._version = str(output[0]['result']['version'])
        match = re.match('[\d.\d]+', output[0]['result']['version'])
        if match:
            self._version_number = str(match.group(0))
        else:
            self._version_number = str(output[0]['result']['version'])
        # Parse out model number
        match = re.search('\d\d\d\d', output[0]['result']['modelName'])
        if match:
            self._model = str(match.group(0))
        else:
            self._model = str(output[0]['result']['modelName'])

    def enable_authentication(self, password):
        """Configures the enable mode authentication password

        EOS supports an additional password authentication mechanism for
        sessions that want to switch to executive (or enable) mode.  This
        method will configure the password, if required, for entering
        executive mode

        Args:
            password (str): The password string in clear text used to
                authenticate to exec mode
        """
        self._enablepwd = str(password).strip()

    def config(self, commands, **kwargs):
        """Configures the node with the specified commands

        This method is used to send configuration commands to the node.  It
        will take either a string or a list and prepend the necessary commands
        to put the session into config mode.

        Args:
            commands (str, list): The commands to send to the node in config
                mode.  If the commands argument is a string it will be cast to
                a list.
                The list of commands will also be prepended with the
                necessary commands to put the session in config mode.
            **kwargs: Additional keyword arguments for expanded eAPI
                functionality. Only supported eAPI params are used in building
                the request

        Returns:
            The config method will return a list of dictionaries with the
                output from each command.  The function will strip the
                response from any commands it prepends.
        """
        commands = make_iterable(commands)
        commands = list(commands)

        # push the configure command onto the command stack
        commands.insert(0, 'configure terminal')
        response = self.run_commands(commands, **kwargs)

        if self.autorefresh:
            self.refresh()

        # pop the configure command output off the stack
        response.pop(0)

        return response

    def section(self, regex, config='running_config'):
        """Returns a section of the config

        Args:
            regex (str): A valid regular expression used to select sections
                of configuration to return
            config (str): The configuration to return.  Valid values for config
                are "running_config" or "startup_config".  The default value
                is "running_config"

        Returns:
            The configuration section as a string object.
        """
        if config in ['running_config', 'startup_config']:
            config = getattr(self, config)
        match = re.search(regex, config, re.M)
        if not match:
            raise TypeError('config section not found')
        block_start, line_end = match.regs[0]

        match = re.search(r'^[^\s]', config[line_end:], re.M)
        if not match:
            raise TypeError('could not find end block')
        _, block_end = match.regs[0]

        block_end = line_end + block_end
        return config[block_start:block_end]

    def enable(self, commands, encoding='json', strict=False,
               send_enable=True, **kwargs):
        """Sends the array of commands to the node in enable mode

        This method will send the commands to the node and evaluate
        the results.  If a command fails due to an encoding error,
        then the command set will be re-issued individual with text
        encoding.

        Args:
            commands (list): The list of commands to send to the node

            encoding (str): The requested encoding of the command output.
                Valid values for encoding are JSON or text

            strict (bool): If False, this method will attempt to run a
                command with text encoding if JSON encoding fails
            send_enable (bool): If True the enable command will be
                               prepended to the command list automatically.
            **kwargs: Additional keyword arguments for expanded eAPI
                functionality. Only supported eAPI params are used in building
                the request

        Returns:
            A dict object that includes the response for each command along
                with the encoding

        Raises:
            TypeError:
                This method does not support sending configure
                commands and will raise a TypeError if configuration commands
                are found in the list of commands provided

                This method will also raise a TypeError if the specified
                encoding is not one of 'json' or 'text'

            CommandError: This method will raise a CommandError if any one
                of the commands fails.
        """
        commands = make_iterable(commands)

        if 'configure' in commands:
            raise TypeError('config mode commands not supported')

        results = list()
        # IMPORTANT: There are two keys (response, result) that both
        # return the same value. 'response' was originally placed
        # there in error and both are now present to avoid breaking
        # existing scripts. 'response' will be removed in a future release.
        if strict:
            responses = self.run_commands(commands, encoding, send_enable,
                                          **kwargs)
            for index, response in enumerate(responses):
                results.append(dict(command=commands[index],
                                    result=response,
                                    response=response,
                                    encoding=encoding))
        else:
            for command in commands:
                try:
                    resp = self.run_commands(command, encoding, send_enable,
                                             **kwargs)
                    results.append(dict(command=command,
                                        result=resp[0],
                                        encoding=encoding))
                except CommandError as exc:
                    if exc.error_code == 1003:
                        resp = self.run_commands(command, 'text', send_enable,
                                                 **kwargs)
                        results.append(dict(command=command,
                                            result=resp[0],
                                            encoding='text'))
                    else:
                        raise
        return results

    def run_commands(self, commands, encoding='json', send_enable=True,
                     **kwargs):
        """Sends the commands over the transport to the device

        This method sends the commands to the device using the nodes
        transport.  This is a lower layer function that shouldn't normally
        need to be used, preferring instead to use config() or enable().

        Args:
            commands (list): The ordered list of commands to send to the
                device using the transport
            encoding (str): The encoding method to use for the request and
                excpected response.
            send_enable (bool): If True the enable command will be
                               prepended to the command list automatically.
            **kwargs: Additional keyword arguments for expanded eAPI
                functionality. Only supported eAPI params are used in building
                the request

        Returns:
            This method will return the raw response from the connection
                which is a Python dictionary object.
        """
        commands = make_iterable(commands)

        # Some commands are multiline commands. These are banner commands and
        # SSL commands. So with this two lines we
        # can support those by passing commands by doing:
        # banner login MULTILINE: This is my banner.\nAnd I even support
        # multiple lines.
        # Why this? To be able to read a configuration from a file, split it
        # into lines and pass it as it is
        # to pyeapi without caring about multiline commands.
        commands = [{'cmd': c.split('MULTILINE:')[0],
                     'input': '%s\n' % (c.split('MULTILINE:')[1].strip())}
                    if 'MULTILINE:' in c else c for c in commands]

        if send_enable:
            if self._enablepwd:
                commands.insert(0, {'cmd': 'enable', 'input': self._enablepwd})
            else:
                commands.insert(0, 'enable')

        response = self._connection.execute(commands, encoding, **kwargs)

        # pop enable command from the response only if we sent enable
        if send_enable:
            response['result'].pop(0)

        return response['result']

    def api(self, name, namespace='pyeapi.api'):
        """Loads the specified api module

        This method is the API autoload mechanism that will load the API
        module specified by the name argument.  The API module will be loaded
        and look first for an initialize() function and secondly for an
        instance() function.  In both cases, the node object is passed to
        the module.

        Args:
            name (str): The name of the module to load.  The name should be
                the name of the python file to import
            namespace (str): The namespace to use to load the module.  The
                default value is 'pyeapi.api'

        Returns:
            The API module loaded with the node instance.
        """
        module = load_module('{}.{}'.format(namespace, name))
        if hasattr(module, 'initialize'):
            module.initialize(self)
        if hasattr(module, 'instance'):
            return module.instance(self)
        return module

    def get_config(self, config='running-config', params=None,
                   as_string=False):
        """ Retreives the config from the node

        This method will retrieve the config from the node as either a string
        or a list object.  The config to retrieve can be specified as either
        the startup-config or the running-config.

        Args:
            config (str): Specifies to return either the nodes startup-config
                or running-config.  The default value is the running-config
            params (str): A string of keywords to append to the command for
                retrieving the config.
            as_string (boo): Flag that determines the response.  If True, then
                the configuration is returned as a raw string.  If False, then
                the configuration is returned as a list.  The default value is
                False

        Returns:
            This method will return either a string or a list depending on the
            states of the as_string keyword argument.

        Raises:
            TypeError: If the specified config is not one of either
                'running-config' or 'startup-config'
        """
        if config not in ['startup-config', 'running-config']:
            raise TypeError('invalid config name specified')

        command = 'show %s' % config
        if params:
            command += ' %s' % params

        result = self.run_commands(command, 'text')
        if as_string:
            return str(result[0]['output']).strip()

        return str(result[0]['output']).split('\n')

    def refresh(self):
        """Refreshes the instance config properties

        This method will refresh the public running_config and startup_config
        properites.  Since the properties are lazily loaded, this method will
        clear the current internal instance variables.  One the next call the
        instance variables will be repopulated with the current config

        """
        self._running_config = None
        self._startup_config = None


def connect_to(name):
    """Creates a node instance based on an entry from the config

    This function will retrieve the settings for the specified connection
    from the config and return a Node instance.  The configuration must
    be loaded prior to calling this function.

    Args:
        name (str): The name of the connection to load from the config.  The
            name argument should be the connection name (everything right of
            the colon from the INI file)

    Returns:
        This function will return an instance of Node with the settings
            from the config instance.

    Raises:
        AttributeError: raised if the specified configuration name is not
            found in the loaded configuration

    """
    kwargs = config_for(name)

    if not kwargs:
        raise AttributeError('connection profile not found in config')

    node = connect(return_node=True, **kwargs)
    return node