diff --git a/CHANGELOG.md b/CHANGELOG.md index 38eb435..14e2d84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,21 @@ -# Change Log +# Changelog -## [1.2](https://github.com/arista-eosplus/rbeapi/tree/1.2) (2017-06-02) -[Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v1.1...1.2) +## [1.3.0](https://github.com/arista-eosplus/rbeapi/tree/1.3.0) (2018-04-03) + +[Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v1.2...1.3.0) + +**Implemented enhancements:** + +- logging: support vrf, port, and protocol options [\#173](https://github.com/arista-eosplus/rbeapi/issues/173) +- \(NETDEV-30\) Support new properties in Types [\#174](https://github.com/arista-eosplus/rbeapi/pull/174) ([jerearista](https://github.com/jerearista)) + +**Merged pull requests:** + +- Release 1.2.0 [\#170](https://github.com/arista-eosplus/rbeapi/pull/170) ([jerearista](https://github.com/jerearista)) + +## [v1.2](https://github.com/arista-eosplus/rbeapi/tree/v1.2) (2017-06-03) + +[Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v1.1...v1.2) **Implemented enhancements:** @@ -13,6 +27,7 @@ - \(NETDEV-29\) Enhance netdev NTP api [\#169](https://github.com/arista-eosplus/rbeapi/pull/169) ([shermdog](https://github.com/shermdog)) ## [v1.1](https://github.com/arista-eosplus/rbeapi/tree/v1.1) (2016-12-06) + [Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v1.0...v1.1) **Implemented enhancements:** @@ -31,6 +46,7 @@ - extend and fix ospf features [\#156](https://github.com/arista-eosplus/rbeapi/pull/156) ([rknaus](https://github.com/rknaus)) ## [v1.0](https://github.com/arista-eosplus/rbeapi/tree/v1.0) (2016-09-26) + [Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.5.1...v1.0) **Implemented enhancements:** @@ -75,6 +91,7 @@ - Created vlans set\_trunk\_groups method. [\#119](https://github.com/arista-eosplus/rbeapi/pull/119) ([devrobo](https://github.com/devrobo)) ## [v0.5.1](https://github.com/arista-eosplus/rbeapi/tree/v0.5.1) (2016-02-16) + [Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.5.0...v0.5.1) **Implemented enhancements:** @@ -104,6 +121,7 @@ - Added support for setting system banners. [\#104](https://github.com/arista-eosplus/rbeapi/pull/104) ([devrobo](https://github.com/devrobo)) ## [v0.5.0](https://github.com/arista-eosplus/rbeapi/tree/v0.5.0) (2016-01-12) + [Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.4.0...v0.5.0) **Implemented enhancements:** @@ -140,6 +158,7 @@ - Update documentation [\#97](https://github.com/arista-eosplus/rbeapi/pull/97) ([HuntBurdick](https://github.com/HuntBurdick)) ## [v0.4.0](https://github.com/arista-eosplus/rbeapi/tree/v0.4.0) (2015-11-21) + [Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.3.0...v0.4.0) **Implemented enhancements:** @@ -177,6 +196,7 @@ - add dry-run mode [\#42](https://github.com/arista-eosplus/rbeapi/pull/42) ([kakkotetsu](https://github.com/kakkotetsu)) ## [v0.3.0](https://github.com/arista-eosplus/rbeapi/tree/v0.3.0) (2015-08-24) + [Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.2.0...v0.3.0) **Fixed bugs:** @@ -196,6 +216,7 @@ - Eliminate overloading value option in command\_builder. [\#39](https://github.com/arista-eosplus/rbeapi/pull/39) ([devrobo](https://github.com/devrobo)) ## [v0.2.0](https://github.com/arista-eosplus/rbeapi/tree/v0.2.0) (2015-07-08) + [Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.1.0...v0.2.0) **Implemented enhancements:** @@ -239,5 +260,8 @@ ## [v0.1.0](https://github.com/arista-eosplus/rbeapi/tree/v0.1.0) (2015-02-25) +[Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/63b0bf8c005b4eab53036556d7becf9b1a7f4bb4...v0.1.0) + + -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/Gemfile b/Gemfile index 9c1664b..ae530f4 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,7 @@ end group :development, :test do gem 'ci_reporter_rspec', require: false + gem 'github_changelog_generator', :git => 'https://github.com/skywinder/github-changelog-generator.git' gem 'listen', '<=3.0.3' gem 'pry', require: false gem 'pry-doc', require: false diff --git a/README.md b/README.md index 8e0c1e8..8497e43 100644 --- a/README.md +++ b/README.md @@ -250,29 +250,29 @@ omnibus installation package in /opt/chef/bin/. For Chef, use the Examples: Puppet Open Source: cd /mnt/flash; \ - swix create rbeapi-1.2-1.swix \ - rubygem-rbeapi-1.2-1.eos4.noarch.rpm \ + swix create rbeapi-1.3-1.swix \ + rubygem-rbeapi-1.3-1.eos4.noarch.rpm \ rubygem-inifile-3.0.0-3.eos4.noarch.rpm \ rubygem-netaddr-1.5.0-2.eos4.noarch.rpm \ rubygem-net_http_unix-0.2.1-3.eos4.noarch.rpm Puppet-enterprise agent (3.x): cd/mnt/flash; \ - swix create rbeapi-puppet3-1.2-1.swix \ - rubygem-rbeapi-puppet3-1.2-1.eos4.noarch.rpm \ + swix create rbeapi-puppet3-1.3-1.swix \ + rubygem-rbeapi-puppet3-1.3-1.eos4.noarch.rpm \ rubygem-inifile-puppet3-3.0.0-3.eos4.noarch.rpm \ rubygem-netaddr-puppet3-1.5.0-2.eos4.noarch.rpm Puppet-All-in-one agent (2015.x/4.x): cd/mnt/flash; \ - swix create rbeapi-puppet-aio-1.2-1.swix \ - rubygem-rbeapi-puppet-aio-1.2-1.eos4.noarch.rpm \ + swix create rbeapi-puppet-aio-1.3-1.swix \ + rubygem-rbeapi-puppet-aio-1.3-1.eos4.noarch.rpm \ rubygem-inifile-puppet-aio-3.0.0-3.eos4.noarch.rpm \ rubygem-netaddr-puppet-aio-1.5.0-2.eos4.noarch.rpm \ rubygem-net_http_unix-puppet-aio-0.2.1-3.eos4.noarch.rpm Chef client: cd /mnt/flash; \ - swix create rbeapi-chef-1.2-1.swix \ - rubygem-rbeapi-chef-1.2-1.eos4.noarch.rpm \ + swix create rbeapi-chef-1.3-1.swix \ + rubygem-rbeapi-chef-1.3-1.eos4.noarch.rpm \ rubygem-inifile-chef-3.0.0-5.eos4.noarch.rpm \ rubygem-netaddr-chef-1.5.1-4.eos4.noarch.rpm \ rubygem-net_http_unix-chef-0.2.2-5.eos4.noarch.rpm @@ -283,13 +283,13 @@ omnibus installation package in /opt/chef/bin/. For Chef, use the Arista# copy flash: Arista# bash -bash-4.1# cd /mnt/flash/ - -bash-4.1# swix create rbeapi-puppet3-1.2-1.swix \ - rubygem-rbeapi-puppet3-1.2-1.eos4.noarch.rpm \ + -bash-4.1# swix create rbeapi-puppet3-1.3-1.swix \ + rubygem-rbeapi-puppet3-1.3-1.eos4.noarch.rpm \ rubygem-inifile-puppet3-3.0.0-1.eos4.noarch.rpm \ rubygem-netaddr-puppet3-1.5.0-1.eos4.noarch.rpm -bash-4.1# exit - Arista# copy flash:rbeapi-puppet3-1.2-1.swix extension: - Arista# extension rbeapi-puppet3-1.2-1.swix + Arista# copy flash:rbeapi-puppet3-1.3-1.swix extension: + Arista# extension rbeapi-puppet3-1.3-1.swix Arista# copy installed-extensions boot-extensions ``` @@ -298,7 +298,7 @@ omnibus installation package in /opt/chef/bin/. For Chef, use the On EOS: ``` Arista# no extension pe-rbeapi-1.1.0-1.swix - Arista# extension rbeapi-puppet3-1.2-1.swix + Arista# extension rbeapi-puppet3-1.3-1.swix Arista# copy installed-extensions boot-extensions ``` diff --git a/lib/rbeapi/api/logging.rb b/lib/rbeapi/api/logging.rb index ee5da73..2669fa1 100644 --- a/lib/rbeapi/api/logging.rb +++ b/lib/rbeapi/api/logging.rb @@ -40,6 +40,16 @@ module Api ## # The Logging class manages logging settings on an EOS node. class Logging < Entity + SEV_NUM = { + 'emergencies' => 0, + 'alerts' => 1, + 'critical' => 2, + 'errors' => 3, + 'warnings' => 4, + 'notifications' => 5, + 'informational' => 6, + 'debugging' => 7 + }.freeze ## # get returns the current logging configuration hash extracted from the # nodes running configuration. @@ -55,6 +65,10 @@ class Logging < Entity def get response = {} response.merge!(parse_enable) + response.merge!(parse_console_level) + response.merge!(parse_monitor_level) + response.merge!(parse_timestamp_units) + response.merge!(parse_source) response.merge!(parse_hosts) response end @@ -73,6 +87,79 @@ def parse_enable value = /no logging on/ !~ config { enable: value } end + private :parse_enable + + ## + # parse_console_level scans the nodes current running configuration and + # extracts the current enabled state of the logging facility. The logging + # enable command is expected to always be in the node's configuration. + # This methods return value is intended to be merged into the logging + # resource hash. + # + # @api private + # + # @return [Hash] Returns the resource hash attribute. + def parse_console_level + level = config.scan(/^logging console ([^\s]+)/).first + { console: SEV_NUM[level[0]] } + end + private :parse_console_level + + ## + # parse_monitor_level scans the nodes current running configuration and + # extracts the current enabled state of the logging facility. The + # logging enable command is expected to always be in the node's + # configuration. This methods return value is intended to be merged into + # the logging resource hash. + # + # @api private + # + # @return [Hash] Returns the resource hash attribute. + def parse_monitor_level + level = config.scan(/^logging monitor ([^\s]+)/).first + { monitor: SEV_NUM[level[0]] } + end + private :parse_monitor_level + + ## + # parse_timestamp_units scans the nodes current running configuration + # and extracts the current configured value of the logging timestamps. + # The logging timestamps command is expected to always be in the node's + # configuration. This methods return value is intended to be merged into + # the logging resource hash. + # + # @api private + # + # @return [Hash] Returns the resource hash attribute. + def parse_timestamp_units + value = config.scan(/^logging format timestamp ([^\s]+)/).first + units = value[0] == 'traditional' ? 'seconds' : 'milliseconds' + { time_stamp_units: units } + end + private :parse_timestamp_units + + ## + # parse_source scans the nodes' current running configuration and extracts + # the configured logging source interfaces if any are configured. If no + # logging sources are configured, then the value will be an empty + # array. The return value requires conversion from a hash to a pair of + # ordered arrays to be merged into the logging resource hash. + # + # @api private + # + # @return [Hash] Returns the resource hash attribute. + def parse_source + entries = config.scan( + /^logging(?:\svrf\s([^\s]+))?\ssource-interface\s([^\s]+)/ + ) + sources = {} + entries.each do |vrf, intf| + vrf = vrf.nil? ? 'default' : vrf + sources[vrf.to_s] = intf + end + { source: sources } + end + private :parse_source ## # parse_hosts scans the nodes current running configuration and extracts @@ -85,7 +172,17 @@ def parse_enable # # @return [Hash] Returns the resource hash attribute. def parse_hosts - hosts = config.scan(/(?<=^logging\shost\s)[^\s]+/) + entries = config.scan( + /^logging(?:\svrf\s([^\s]+))?\shost\s([^\s]+)\s(\d+) + \sprotocol\s([^\s]+)/x + ) + hosts = [] + entries.each do |vrf, address, port, proto| + hosts << { address: address, + vrf: vrf.nil? ? 'default' : vrf, + port: port, + protocol: proto } + end { hosts: hosts } end private :parse_hosts @@ -119,6 +216,104 @@ def set_enable(opts = {}) configure cmd end + ## + # set_console configures the global logging level for the console. + # If the default keyword is specified and set to true, then the + # configuration is defaulted using the default keyword. The default + # keyword option takes precedence over the enable keyword if both + # options are specified. + # + # @since eos_version 4.13.7M + # + # ===Commands + # logging console + # no logging console + # default logging console + # + # @param opts [Hash] Optional keyword arguments + # + # @option opts level [Int|String] Enables logging at the specified + # level. Accepts <0-7> and logging level keywords. + # + # @option opts default [Boolean] Resets the monitor level to the + # default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_console(opts = {}) + cmd = 'logging console' + cmd += " #{opts[:level]}" if opts[:level] + cmd = command_builder(cmd, opts) + configure cmd + end + + ## + # set_monitor configures the global logging level for terminals + # If the default keyword is specified and set to true, then the + # configuration is defaulted using the default keyword. The default + # keyword option takes precedence over the enable keyword if both + # options are specified. + # + # @since eos_version 4.13.7M + # + # ===Commands + # logging monitor + # no logging monitor + # default logging monitor + # + # @param opts [Hash] Optional keyword arguments + # + # @option opts level [Int|String] Enables logging at the specified + # level. Accepts <0-7> and logging level keywords. + # + # @option opts default [Boolean] Resets the monitor level to the + # default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_monitor(opts = {}) + cmd = 'logging monitor' + cmd += " #{opts[:level]}" if opts[:level] + cmd = command_builder(cmd, opts) + configure cmd + end + + ## + # set_time_stamp_units configures the global logging time_stamp_units + # If the default keyword is specified and set to true, then the + # configuration is defaulted using the default keyword. The default + # keyword option takes precedence over the enable keyword if both + # options are specified. + # + # @since eos_version 4.13.7M + # + # ===Commands + # logging format timestamp + # no logging format timestamp + # default logging format timestamp + # + # @param opts [Hash] Optional keyword arguments + # + # @option opts units [String] Enables logging timestamps with the + # specified units. One of 'traditional' | 'seconds' or + # 'high-resolution' | 'milliseconds' + # + # @option opts default [Boolean] Resets the logging timestamp level to + # the default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_time_stamp_units(opts = {}) + unit_map = { + 'traditional' => ' traditional', + 'seconds' => ' traditional', + 'high-resolution' => ' high-resolution', + 'milliseconds' => ' high-resolution' + } + units = '' + units = unit_map[opts[:units]] if opts[:units] + cmd = "logging format timestamp#{units}" + cmd = command_builder(cmd, opts) + configure cmd + end + ## # add_host configures a new logging destination host address or hostname # to the list of logging destinations. If the host is already configured @@ -133,8 +328,12 @@ def set_enable(opts = {}) # node to send logging information to. # # @return [Boolean] Returns true if the command completed successfully. - def add_host(name) - configure "logging host #{name}" + def add_host(name, opts = {}) + vrf = opts[:vrf] ? "vrf #{opts[:vrf]} " : '' + cmd = "logging #{vrf}host #{name}" + cmd += " #{opts[:port]}" if opts[:port] + cmd += " protocol #{opts[:protocol]}" if opts[:protocol] + configure cmd end ## @@ -151,8 +350,11 @@ def add_host(name) # host to remove from the nodes current configuration. # # @return [Boolean] Returns true if the commands completed successfully. - def remove_host(name) - configure "no logging host #{name}" + def remove_host(name, opts = {}) + vrf = opts[:vrf] ? "vrf #{opts[:vrf]} " : '' + # Hosts are uniquely identified by vrf and address, alone. + cmd = "no logging #{vrf}host #{name}" + configure cmd end end end diff --git a/lib/rbeapi/api/radius.rb b/lib/rbeapi/api/radius.rb index 83f6008..efe88e3 100644 --- a/lib/rbeapi/api/radius.rb +++ b/lib/rbeapi/api/radius.rb @@ -76,6 +76,7 @@ def get global.merge!(parse_global_timeout) global.merge!(parse_global_retransmit) global.merge!(parse_global_key) + global.merge!(parse_global_source) resource = { global: global, servers: parse_servers } resource end @@ -126,6 +127,29 @@ def parse_global_key end private :parse_global_key + ## + # parse_global_source takes a running configuration as a string + # and parses out the radius global source-interface per VRF if it exists + # in the configuration. An empty Hash is returned if there is no global + # setting configured. The Hash needs to be converted to ordered lists + # for vrf and source_interface before being merged into a property hash. + # + # @api private + # + # @return [Hash] Returns the resource hash attribute. + def parse_global_source + src = config.scan(/ip radius(?:\svrf\s(\w+))?\ssource-interface\s(\w+)/) + sources = {} + src.each do |vrf, intf| + vrf = vrf.nil? ? 'default' : vrf + sources[vrf] = intf + end + { source_interface: sources } + end + private :parse_global_source + + ## + ## # parse_servers returns an Array of radius server resource hashes. Each # hash describes the current state of the radius server and is intended @@ -335,6 +359,32 @@ def remove_server(opts = {}) cmd << " acct-port #{opts[:acct_port]}" if opts[:acct_port] configure cmd end + + ## + # set_source_interface takes a dictionary mapping the VRF to the desired + # source interface. Any radius source-interface lines in the + # running-config that are not defined in the hash will be removed, then + # lines generated from the hash will be applied. This is NOT idempotent, + # however, it is explicit. + # + # @api public + # + # @param sources [Hash] A hash mapping the vrf name to the source + # interface. + # + # @return [Boolean] Returns true if there are no errors. + def set_source_interface(sources) + existing = config.scan(/ip radius.* source-interface.*/) + desired = [] + sources.each do |vrf, intf| + vrf_str = vrf == 'default' ? '' : " vrf #{vrf}" + desired << "ip radius#{vrf_str} source-interface #{intf}" + end + remove = existing - desired + cmds = remove.map { |line| "no #{line}" } + cmds.concat(desired) + configure cmds + end end end end diff --git a/lib/rbeapi/api/tacacs.rb b/lib/rbeapi/api/tacacs.rb index eed5843..57ab82c 100644 --- a/lib/rbeapi/api/tacacs.rb +++ b/lib/rbeapi/api/tacacs.rb @@ -77,6 +77,7 @@ def get global = {} global.merge!(parse_global_timeout) global.merge!(parse_global_key) + global.merge!(parse_global_source) resource = { global: global, servers: servers } resource end @@ -116,6 +117,27 @@ def parse_global_timeout end private :parse_global_timeout + ## + # parse_global_source takes a running configuration as a string + # and parses out the tacacs global source-interface per VRF if it exists + # in the configuration. An empty Hash is returned if there is no global + # setting configured. The Hash needs to be converted to ordered lists + # for vrf and source_interface before being merged into a property hash. + # + # @api private + # + # @return [Hash] Returns the resource hash attributes. + def parse_global_source + src = config.scan(/ip tacacs(?:\svrf\s(\w+))?\ssource-interface\s(\w+)/) + sources = {} + src.each do |vrf, intf| + vrf = vrf.nil? ? 'default' : vrf + sources[vrf] = intf + end + { source_interface: sources } + end + private :parse_global_source + ## # servers returns an Array of tacacs server resource hashes. Each hash # describes the current state of the tacacs server and is suitable for @@ -243,6 +265,32 @@ def remove_server(opts = {}) cmd << " port #{opts[:port]}" if opts[:port] configure cmd end + + ## + # set_source_interface takes a dictionary mapping the VRF to the desired + # source interface. Any tacacs source-interface lines in the + # running-config that are not defined in the hash will be removed, then + # lines generated from the hash will be applied. This is NOT idempotent, + # however, it is explicit. + # + # @api public + # + # @param sources [Hash] A hash mapping the vrf name to the source + # interface. + # + # @return [Boolean] Returns true if there are no errors. + def set_source_interface(sources) + existing = config.scan(/ip tacacs.* source-interface.*/) + desired = [] + sources.each do |vrf, intf| + vrf_str = vrf == 'default' ? '' : " vrf #{vrf}" + desired << "ip tacacs#{vrf_str} source-interface #{intf}" + end + remove = existing - desired + cmds = remove.map { |line| "no #{line}" } + cmds.concat(desired) + configure cmds + end end end end diff --git a/lib/rbeapi/version.rb b/lib/rbeapi/version.rb index cd67d84..ce9ae7f 100644 --- a/lib/rbeapi/version.rb +++ b/lib/rbeapi/version.rb @@ -33,5 +33,5 @@ # # # Rbeapi toplevel namespace. module Rbeapi - VERSION = '1.2'.freeze + VERSION = '1.3'.freeze end