Skip to content

Commit

Permalink
Merge pull request #305 from arista-eosplus/release-1.0.4
Browse files Browse the repository at this point in the history
Release 1.0.4
  • Loading branch information
dlyssenko authored Aug 20, 2024
2 parents 21d720f + 4e75a7c commit 03ce49f
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: CI
on: [ pull_request, workflow_dispatch ]
on: [ pull_request, workflow_dispatch, pull_request_target ]
jobs:
build:
runs-on: ubuntu-latest
Expand Down
6 changes: 5 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ build:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"
jobs:
pre_build:
- make -C docs modules


# Build documentation in the "docs/" directory with Sphinx
sphinx:
Expand All @@ -33,4 +37,4 @@ python:
install:
- requirements: dev-requirements.txt
- method: pip
path: .
path: .
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.3
1.0.4
1 change: 0 additions & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
-r requirements.txt
mock
coveralls
twine
check-manifest
Expand Down
2 changes: 1 addition & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ html:
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

modules:
python $(CWD)/generate_modules.py
python3 $(CWD)/generate_modules.py

docs: clean modules html

Expand Down
22 changes: 22 additions & 0 deletions docs/release-notes-1.0.4.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Release 1.0.4
-------------

2024-08-19
\- the major reason for this release is to fix documentation issue on `readthedocs <https://pyeapi.readthedocs.io/en/latest/>`_ site

New Modules
^^^^^^^^^^^

Enhancements
^^^^^^^^^^^^

Fixed
^^^^^
* System test fixes ( `#285 <https://github.com/arista-eosplus/pyeapi/pull/285>`_, `#291 <https://github.com/arista-eosplus/pyeapi/pull/291>`_,
`#302 <https://github.com/arista-eosplus/pyeapi/pull/302>`_, `#303 <https://github.com/arista-eosplus/pyeapi/pull/303>`_ )
* Fixed PR `#289 <https://github.com/arista-eosplus/pyeapi/pull/289>`_: allow specifying API version in requests.
* Fixed PR `#286 <https://github.com/arista-eosplus/pyeapi/pull/291>`_: a regression introduced by PR #220, where parsing a non-empty banner section may fail
* Fixed *modules* section on `readthedocs <https://pyeapi.readthedocs.io/en/develop/modules.html>`_ site (PR `#300 <https://github.com/arista-eosplus/pyeapi/pull/300>`_)

Known Caveats
^^^^^^^^^^^^^
2 changes: 1 addition & 1 deletion pyeapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
__version__ = '1.0.3'
__version__ = '1.0.4'
__author__ = 'Arista EOS+'


Expand Down
46 changes: 29 additions & 17 deletions pyeapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,27 +718,39 @@ def _chunkify( self, config, indent=0 ):
last parsed (sub)section, which in turn may contain sub-sections
"""
def is_subsection_present( section, indent ):
return any( [line[ indent ] == ' ' for line in section] )
return any( line[ indent ] == ' ' for line in section )

def get_indent( line ):
return len( line ) - len( line.lstrip() )

sections = {}
key = None
banner = None
for line in config.splitlines( keepends=True )[ indent > 0: ]:
# indent > 0: no need processing subsection line, which is 1st line
if line[ indent ] == ' ': # section continuation
sections[key] += line
line_rs = line.rstrip()
if indent == 0:
if banner:
sections[ banner ] += line
if line_rs == 'EOF':
banner = None
continue
if line.startswith( 'banner ' ):
banner = line_rs
sections[ banner ] = line
continue
if get_indent( line_rs ) > indent: # i.e. subsection line
# key is always expected to be set by now
sections[ key ] += line
continue
# new section is found (if key is not None)
if key: # process prior (last recorded) section
lines = sections[key].splitlines()[ 1: ]
if len( lines ): # section may contain sub-sections
ind = len( lines[0] ) - len( lines[0].lstrip() )
if is_subsection_present( lines, ind ):
subs = self._chunkify( sections[key], indent=ind )
subs.update( sections )
sections = subs
elif indent > 0: # record only subsections
del sections[key]
key = line.rstrip()
sections[key] = line
subsection = sections.get( key, '' ).splitlines()[ 1: ]
if subsection:
sub_indent = get_indent( subsection[0] )
if is_subsection_present( subsection, sub_indent ):
parsed = self._chunkify( sections[key], indent=sub_indent )
parsed.update( sections )
sections = parsed
key = line_rs
sections[ key ] = line
return sections

def section(self, regex, config='running_config'):
Expand Down
2 changes: 2 additions & 0 deletions pyeapi/eapilib.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ def request(self, commands, encoding=None, reqid=None, **kwargs):
reqid = id(self) if reqid is None else reqid
params = {'version': 1, 'cmds': commands, 'format': encoding}
streaming = False
if 'apiVersion' in kwargs:
params['version'] = kwargs['apiVersion']
if 'autoComplete' in kwargs:
params['autoComplete'] = kwargs['autoComplete']
if 'expandAliases' in kwargs:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
# $ pip install -e .[dev,test]
extras_require={
'dev': ['check-manifest', 'pep8', 'pyflakes', 'twine'],
'test': ['coverage', 'mock'],
'test': ['coverage'],
},
)

Expand Down
26 changes: 16 additions & 10 deletions test/fixtures/running_config.text
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,22 @@ vlan 300
state active
no private-vlan
!
banner login
+++++++++++++++++++++++++++++++++++++++++
banner:

vlan 1
this
is the loging ban
that would b emult
EOF
!

banner motd
this text
can be multine
EOF
!
interface Port-Channel10
no description
no shutdown
Expand Down Expand Up @@ -2106,16 +2122,6 @@ no ip tacacs source-interface
!
no vxlan vni notation dotted
!
banner login
this
is the loging ban
that would b emult
EOF
banner motd
this text
can be multine
EOF
!
system coredump compressed
!
no dot1x system-auth-control
Expand Down
22 changes: 18 additions & 4 deletions test/lib/systestlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import random

from testlib import get_fixture
from pyeapi.utils import CliVariants

import pyeapi.client

Expand All @@ -48,19 +49,32 @@ def setUp(self):

self.duts = list()
for name in config.sections():
if name.startswith('connection:') and 'localhost' not in name:
name = name.split(':')[1]
self.duts.append(pyeapi.client.connect_to(name))
if not name.startswith('connection:'):
continue
if 'localhost' in name:
continue
name = name.split(':')[1]
self.duts.append( pyeapi.client.connect_to(name) )
# revert to a legacy behavior for interface availability
if self.duts[ -1 ]:
self.duts[ -1 ].config( CliVariants(
'service interface inactive expose', 'enable') )

def sort_dict_by_keys(self, d):
keys = sorted(d.keys())
return dict([(k, d[k]) for k in keys])


def random_interface(dut, exclude=None):
# interfaces read in 'show run all' and those actually present may differ,
# thus interface list must be picked from the actually present
if not getattr( random_interface, 'present', False ):
random_interface.present = dut.run_commands(
'show interfaces', send_enable=False )[ 0 ][ 'interfaces' ].keys()
exclude = [] if exclude is None else exclude
interfaces = dut.api('interfaces')
names = [name for name in list(interfaces.keys()) if name.startswith('Et')]
names = [ name for name in list(interfaces.keys()) if name.startswith('Et') ]
names = [ name for name in names if name in random_interface.present ]

exclude_interfaces = dut.settings.get('exclude_interfaces', [])
if exclude_interfaces:
Expand Down
6 changes: 3 additions & 3 deletions test/system/test_api_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class TestResourceInterfaces(DutSystemTest):

def test_get(self):
for dut in self.duts:
intf = random_interface(dut)
intf = random_interface( dut, exclude=['Ethernet1'] )
dut.config(['default interface %s' % intf,
'interface %s' % intf,
'description this is a test',
Expand Down Expand Up @@ -390,7 +390,7 @@ def test_get_lacp_mode_with_default(self):

def test_minimum_links_valid(self):
for dut in self.duts:
minlinks = random_int(1, 16)
minlinks = random_int(1, 8) # some physical duts may have only 8 links
dut.config(['no interface Port-Channel1',
'interface Port-Channel1'])
result = dut.api('interfaces').set_minimum_links('Port-Channel1',
Expand All @@ -403,7 +403,7 @@ def test_minimum_links_valid(self):

def test_minimum_links_invalid_value(self):
for dut in self.duts:
minlinks = random_int(129, 256) # some duts may support up to 128
minlinks = 1025 # hope it will hold for a while
result = dut.api(
'interfaces').set_minimum_links('Port-Channel1', minlinks)
self.assertFalse(result)
Expand Down
4 changes: 2 additions & 2 deletions test/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import sys
import os
import unittest
import imp
import importlib

sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))

Expand Down Expand Up @@ -227,7 +227,7 @@ class TestClient(unittest.TestCase):
def setUp(self):
if 'EAPI_CONF' in os.environ:
del os.environ['EAPI_CONF']
imp.reload(pyeapi.client)
importlib.reload(pyeapi.client)

def test_load_config_for_connection_with_filename(self):
conf = get_fixture('eapi.conf')
Expand Down

0 comments on commit 03ce49f

Please sign in to comment.