This file is indexed.

/usr/lib/python2.7/dist-packages/pyEOS/eos.py is in python-pyeos 0.63-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
# Copyright 2014 Spotify AB. All rights reserved.
#
# The contents of this file are licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

from jsonrpclib import Server
from jsonrpclib import ProtocolError

import exceptions


class EOS:
    def __init__(self, hostname, username, password, use_ssl=True):
        """
        Represents a device running EOS.

        The object will contain the following interesting attributes:

        * **running_config** - The configuration retrieved from the device using the method load_running_config
        * **candidate_config** - The configuration we desire for the device. Can be populated using the method load_candidate_config

        :param hostname: IP or FQDN of the device you want to connect to
        :param username: Username
        :param password: Password
        :param use_ssl: If set you True we will connect to the eAPI using https, otherwise http will be used
        """
        self.hostname = hostname
        self.username = username
        self.device = None
        self.password = password
        self.use_ssl = use_ssl
        self.candidate_config = None
        self.original_config = None

    def __getattr__(self, item):
        def wrapper(*args, **kwargs):
            pipe = kwargs.pop('pipe', None)

            if pipe is None:
                cmd = [item.replace('_', ' ')]
            else:
                cmd = ['{} | {}'.format(item.replace('_', ' '), pipe)]
            return self.run_commands(cmd, **kwargs)[1]

        if item.startswith('show'):
            return wrapper
        else:
            raise AttributeError("type object '%s' has no attribute '%s'" % (self.__class__.__name__, item))

    @staticmethod
    def _load_file(filename):
        string = ''
        with open(filename, 'r') as f:
            for line in f.readlines():
                if line.strip() != '':
                    string += '{}\n'.format(line.strip())

            return string

    def open(self):
        """
        Opens the connection with the device.
        """
        if self.use_ssl:
            url = 'https://%s:%s@%s/command-api' % (self.username, self.password, self.hostname)
        else:
            url = 'http://%s:%s@%s/command-api' % (self.username, self.password, self.hostname)

        self.device = Server(url)

    def run_commands(self, commands, version=1, auto_format=False, format='json', timestamps=True):
        """
        This method will run as many commands as you want. The 'enable' command will be prepended automatically so you
        don't have to worry about that.

        :param commands: List of commands you want to run
        :param version: Version of the eAPI you want to connect to. By default is 1.
        :param auto_format: If set to True API calls not supporting returning JSON messages will be converted automatically to text. By default is False.
        :param format: Format you want to get; 'json' or 'text'. By default is json. This will trigger a CommandUnconverted exception if set to 'json' and auto_format is set to False. It will return text if set to 'json' but auto_format is set to True.
        :param timestamps: This will return some useful information like when was the command executed and how long it took.

        """

        if 'enable' is not commands[0]:
            commands.insert(0, 'enable')

        if auto_format:
            format = 'json'

        try:
            result = self.device.runCmds(
                version=version,
                cmds=commands,
                format=format,
                timestamps=timestamps,
            )
        except ProtocolError as e:
            code = e[0][0]
            error = e[0][1]

            if code == 1003:
                # code 1003 means the command is not yet converted to json
                if auto_format:
                    result = self.device.runCmds(
                        version=version,
                        cmds=commands,
                        format='text',
                        timestamps=timestamps
                    )
                else:
                    raise exceptions.CommandUnconverted(error)
            # code -32602 means "Unexpected parameter 'timestamps' for method 'runCmds' provided"
            elif code == -32602:
                result = self.device.runCmds(
                    version=version,
                    cmds=commands,
                    format=format
                )
            elif code == 1002:
                # code 1002 means the command was wrong
                raise exceptions.CommandError(error)
            elif code == 1000:
                # code 1000 means a command is wrong when doing a "config  replace"
                raise exceptions.ConfigReplaceError(e)
            else:
                raise exceptions.UnknownError((code, error))

        return result

    def close(self):
        """
        Dummy, method. Today it does not do anything but it would be interesting to use it to fake closing a connection.

        """
        pass

    def get_config(self, format='json'):
        """

        :param format: Either 'json' or 'text'
        :return: The running configuration of the device.
        """
        if format == 'json':
            return self.run_commands(['sh running-config'])[1]['cmds']
        elif format == 'text':
            return self.run_commands(['sh running-config'], format='text')[1]['output']

    def load_candidate_config(self, filename=None, config=None):
        """
        Populates the attribute candidate_config with the desired configuration. You can populate it from a file or
        from a string. If you send both a filename and a string containing the configuration, the file takes precedence.

        :param filename: Path to the file containing the desired configuration. By default is None.
        :param config: String containing the desired configuration.
        """

        if filename is not None:
            self.candidate_config = self._load_file(filename)
        else:
            self.candidate_config = config

    def compare_config(self, session=None):
        """

        :return: A string showing the difference between the running_config and the candidate_config. The running_config is
            loaded automatically just before doing the comparison so there is no neeed for you to do it.
        """

        # We get the config in text format because you get better printability by parsing and using an OrderedDict

        cmds = self.candidate_config.splitlines()

        cmds.insert(0, 'configure session pyeos-diff')
        cmds.insert(1, 'rollback clean-config')

        self.run_commands(cmds)

        diff = self.run_commands(['show session-config named pyeos-diff diffs'], format='text')

        self.run_commands(['configure session pyeos-diff', 'abort'])

        return diff[1]['output'][:-1]

    def replace_config(self, config=None, force=False):
        """
        Applies the configuration changes on the device. You can either commit the changes on the candidate_config
        attribute or you can send the desired configuration as a string. Note that the current configuration of the
        device is replaced with the new configuration.

        :param config: String containing the desired configuration. If set to None the candidate_config will be used
        :param force: If set to False we rollback changes if we detect a config error.

        """
        if config is None:
            config = self.candidate_config

        if force:
            force_text = 'ignore-errors'
        else:
            force_text = ''

        body = {
            'cmd': 'configure replace terminal: %s' % force_text,
            'input': config
        }
        self.original_config = self.get_config(format='text')
        result = self.run_commands([body])

        if 'Invalid' not in result[1]['messages'][0]:
            return result
        else:
            raise exceptions.CommandError(result[1]['messages'][0])

    def rollback(self):
        """
        If used after a commit, the configuration will be reverted to the previous state.
        """
        return self.replace_config(config=self.original_config, force=True)