├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── SUGGESTION-BOX └── README.md ├── docs ├── Facts.md ├── Providers │ ├── Group.md │ ├── IPports.md │ ├── L1ports.md │ ├── L2ports.md │ ├── LAGports.md │ ├── StaticHosts.md │ ├── StaticRoutes.md │ ├── UserAuths.md │ ├── Users.md │ └── Vlans.md ├── Providers_Resources.md ├── README_FIRST.md └── Utils │ ├── Config.md │ ├── Filesystem.md │ ├── Routing-Engine.md │ └── SCP.md ├── examples ├── config │ ├── config_file.rb │ ├── config_template_object.rb │ ├── config_template_simple.rb │ ├── load_sample.conf │ ├── load_sample.set │ ├── load_template_main.conf │ ├── load_template_object.conf │ └── multi_config.rb ├── fs_utils.rb ├── lag_port.rb ├── re_upgrade.rb ├── re_utils.rb ├── simple.rb ├── st_hosts.rb ├── user.rb └── vlans.rb ├── junos-ez-stdlib.gemspec ├── lib └── junos-ez │ ├── exceptions.rb │ ├── facts.rb │ ├── facts │ ├── chassis.rb │ ├── ifd_style.rb │ ├── personality.rb │ ├── switch_style.rb │ └── version.rb │ ├── group.rb │ ├── ip_ports.rb │ ├── ip_ports │ └── classic.rb │ ├── l1_ports.rb │ ├── l1_ports │ ├── classic.rb │ └── switch.rb │ ├── l2_ports.rb │ ├── l2_ports │ ├── bridge_domain.rb │ ├── vlan.rb │ └── vlan_l2ng.rb │ ├── lag_ports.rb │ ├── provider.rb │ ├── stdlib.rb │ ├── system.rb │ ├── system │ ├── st_hosts.rb │ ├── st_routes.rb │ ├── syscfg.rb │ ├── userauths.rb │ └── users.rb │ ├── utils │ ├── config.rb │ ├── fs.rb │ └── re.rb │ ├── version.rb │ ├── vlans.rb │ └── vlans │ ├── bridge_domain.rb │ ├── vlan.rb │ └── vlan_l2ng.rb └── spec └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | .ruby-version 11 | *.gem 12 | coverage 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | LineLength: 2 | Enabled: false 3 | 4 | Style/ClassAndModuleChildren: 5 | Enabled: false 6 | 7 | Documentation: 8 | Enabled: false 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 1.9.3-p551 5 | - 2.0.0-p648 6 | - 2.1.9 7 | - 2.2.5 8 | - 2.3.1 9 | matrix: 10 | allow_failures: 11 | - rvm: 1.9.3-p551 12 | - rvm: 2.0.0-p648 13 | fast_finish: true 14 | before_install: gem update --remote bundler 15 | install: 16 | - bundle install --retry=3 17 | script: 18 | - bundle exec rake build 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2013-April 2 | 3 | 0.0.10: 2013-04-26 4 | 5 | Initial release of code into RubyGems. Code tested on EX and SRX-branch. Consider this code 6 | as "early-adopter". Comments/feedback is welcome and appreciated. 7 | 8 | 0.0.11: 2013-04-26 9 | 10 | Updated Junos::Ez::RE::Utils 11 | #memory changed :procs from Hash to Array 12 | #users changed return from Hash to Array 13 | 14 | 0.0.12: 2013-04-26 15 | 16 | Updated Junos::Ez::FS:Utils#ls to include :symlink information 17 | Adding Junos::Ez::Users::Provider for login management 18 | Adding Junos::Ez::UserAuths::Provider for SSH-key management 19 | 20 | 0.0.14: 2013-04-28 21 | 22 | Completed initial documentation. Still more work to be done with these files 23 | but the "enough to get started" information is now available 24 | 25 | # 2013-May 26 | 27 | 0.0.15: 2013-05-02 28 | 29 | L2ports - added support for framework to read [edit vlans] stanza to recognize interfaces configured 30 | there vs. under [edit interfaces] 31 | 32 | IPports - added :acl_in and :acl_out for stateless ACL filtering. added .status method to return 33 | runtime status information about the port 34 | 35 | RE::Utils - misc updates, and documentation 36 | 37 | 0.0.16: 2013-05-03 38 | 39 | RE::Utils - added support for license-key management. Renamed "software" methods from "xxx_software" 40 | to "software_xxx" to be consistent with other naming usage. Updated docs. 41 | 42 | 0.0.17: 2013-05-05 43 | 44 | FS::Utils - updated docs. fixed methods so that all "error" scenarios raise IOError excaptions. 45 | 46 | 0.1.0: 2013-05-06 47 | 48 | All docs and code _finished_ for the inital release of code. Always more to do, but at this 49 | point, declaring the framework "good for early adopter testing". Looking forward to bug-reports, 50 | please open issues against this repo. Thank you! 51 | 52 | 0.1.1: 2013-05-29 53 | 54 | Fixed a small bug in fact gathering for hardwaremodel 55 | 56 | # 2013-July 57 | 58 | 0.1.2: 2013-07-04 59 | 60 | Fixed issue#3. Previously this gem would not work with non-VC capable EX switches. Updated 61 | the `facts/version.rb` file to handle these devices. Also added a new fact `:vc_capable` that 62 | is set to `true` if the EX can support virtual-chassis, and `false` if it cannot. 63 | 64 | # 2013-Aug 65 | 66 | 0.2.0: 67 | 68 | Fixed issue #6. Added support for EX4300 platform. Added new provider for Link Aggregation Group 69 | resources (LAGports) 70 | 71 | # 2016-March 72 | 73 | 1.0.0: 74 | 75 | Fixed issues 76 | Issue #17 Add support for OCX device. 77 | Issue #20 "under development" error is thrown while importing the interface_create recipe from the Chef-Server. 78 | Issue #22 "netdev_vlan" resource action delete is not working fine while invoking from the JUNOS Chef-Client. 79 | Issue #23 RPC command error: commit-configuration is getting thrown on Invoking the "netdev_lag" resource from 80 | JUNOS Chef Client. 81 | Issue #27 Duplicate declaration of lag configuration in a recipe is giving NoMethodError: undefined method 82 | `properties' for nil:NilClass. 83 | Issue #30 Error in rerunning netdev_lag interface. 84 | Issue #33 undefined method `properties' for nil:NilClass error is thrown if the backup RE is unreachable. 85 | Issue #35 Error in running chef client from Backup RE. 86 | Issue #39 Getting 'Junos::Ez::NoProviderError' error on qfx device. 87 | Issue #42 Raise exception to handle warnings in . 88 | 89 | Enhancement 90 | * Add support for configuring l2_interface on MX device. 91 | * Add support for provider 'group' for configuring JUNOS groups. 92 | 93 | # 2016-August 94 | 95 | 1.0.3 96 | Fixed issues 97 | Issue #46 Removing references to rake from gemspec 98 | Issue #47 cannot get the git source code 99 | 100 | Enhancement 101 | * Valid project metadata and rake for cleanly building/releasing 102 | * Test hooks and badges 103 | * netconf gem pessimistic version constraint to 0.3.1 104 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'simplecov', require: false, group: :test 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE (BSD-2) 2 | =============== 3 | Copyright (c) 2013, Jeremy Schulman, Juniper Networks 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in 14 | the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/junos-ez-stdlib.svg)](https://badge.fury.io/rb/junos-ez-stdlib)[![Dependency Status](https://gemnasium.com/badges/github.com/Juniper/ruby-junos-ez-stdlib.svg)](https://gemnasium.com/github.com/Juniper/ruby-junos-ez-stdlib) 2 | [![Build Status](https://travis-ci.org/Juniper/ruby-junos-ez-stdlib.svg?branch=master)](https://travis-ci.org/Juniper/ruby-junos-ez-stdlib) 3 | 4 | # OVERVIEW 5 | 6 | Ruby framework to support Junos OS based device management automation. 7 | 8 | This is the "standard library" or "core" set of functionality that should work on most/all Junos OS based devices. 9 | 10 | This framework is build on top of the NETCONF gem which uses XML as the fundamental data-exchange. So no 11 | "automating the CLI" or using SNMP. The purpose of this framework is to **enable automation development 12 | without requiring specific Junos XML knowledge**. 13 | 14 | Further documentation can be found in the *docs* subdirectory. 15 | 16 | # FRAMEWORK 17 | 18 | The framework is comprised of these basic eloements: 19 | 20 | - Facts: 21 | 22 | A Hash of name/value pairs of information auto-collected. Fact values can be Hash structures as well 23 | so you can have deeply nested fact data. You can also define your own facts in addition to the "stdlib" facts. 24 | The facts are used by the framework to create a platform indepent layer of abstraction. This means 25 | that managing a VLAN, for example, is the same regardless of the underlying hardware platofrm (EX, QFX, 26 | MX, SRX, ...) 27 | 28 | - Resources: 29 | 30 | Resources allow you to easily configure and perform operational functions on specific items within Junos, 31 | for example VLANs, or switch ports. A resource has *properties* that you manipuate as Hash. You can 32 | interact with Junos using resource methods like `read!`, `write!`, `delete!`, `activate!`, `deactivate!`, etc. 33 | For a complete listing of resource methods, refer to the *docs* directory 34 | 35 | - Providers: 36 | 37 | Providers allow you to manage a collection of resource, and most commonly, select a resource. 38 | The purpose of a provider/resource is to automate the life-cycle of common changes, like adding 39 | VLANs, or ports to a VLAN. A provider also allows you to obtain a `list` of resources 40 | (Array of *names*) or a `catalog` (Hash of resource properties). Providers may include resource 41 | specific functionality, like using complex YAML/Hash data for easy import/export and provisioning 42 | with Junos. If you need the ability to simply apply config-snippets that you do not need to model 43 | as resources (as you might for initial device commissioning), the Utilities library is where you 44 | want to start. 45 | 46 | - Utilities: 47 | 48 | Utilities are simply collections of functions. The **configuration** utilities, for example, will 49 | allow you to easily push config snippets in "curly-brace", "set", or XML formats. Very useful 50 | for unmanaged provider/resources (like initial configuration of the device). The 51 | **routing-engine** utilities, for example, will allow you to easily upgrade software, check 52 | memory usage, and do `ping` operations. 53 | 54 | # EXAMPLE USAGE 55 | 56 | ```ruby 57 | require 'pp' 58 | require 'net/netconf/jnpr' 59 | require 'junos-ez/stdlib' 60 | 61 | unless ARGV[0] 62 | puts "You must specify a target" 63 | exit 1 64 | end 65 | 66 | # login information for NETCONF session 67 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 68 | 69 | ## create a NETCONF object to manage the device and open the connection ... 70 | 71 | ndev = Netconf::SSH.new( login ) 72 | print "Connecting to device #{login[:target]} ... " 73 | ndev.open 74 | puts "OK!" 75 | 76 | ## Now bind providers to the device object. The 'Junos::Ez::Provider' must be first. 77 | ## This will retrieve the device 'facts'. The other providers allow you to define the 78 | ## provider variables; so this example is using 'l1_ports' and 'ip_ports', but you could name 79 | ## them what you like, yo! 80 | 81 | Junos::Ez::Provider( ndev ) 82 | Junos::Ez::L1ports::Provider( ndev, :l1_ports ) 83 | Junos::Ez::IPports::Provider( ndev, :ip_ports ) 84 | Junos::Ez::Config::Utils( ndev, :cu ) 85 | 86 | # ----------------------------------------------------------- 87 | # Facts ... 88 | # ----------------------------------------------------------- 89 | 90 | # show the device softare version fact 91 | pp ndev.fact :version 92 | 93 | # show the device serial-number face 94 | pp ndev.fact :serialnumber 95 | 96 | # get a list of all available facts (Array) 97 | pp ndev.facts.list 98 | 99 | # get a hash of all facts and their associated values 100 | pp ndev.facts.catalog 101 | 102 | # ----------------------------------------------------------- 103 | # Layer 1 (physical ports) Resources ... 104 | # ----------------------------------------------------------- 105 | 106 | pp ndev.l1_ports.list 107 | pp ndev.l1_ports.catalog 108 | 109 | # select port 'ge-0/0/0' and display the contents 110 | # of the properties (like port, speed, description) 111 | 112 | ge_0 = ndev.l1_ports['ge-0/0/0'] 113 | pp ge_0.to_h 114 | 115 | # change port to disable, this will write the change 116 | # but not commit it. 117 | 118 | ge_0[:admin] = :down 119 | ge_0.write! 120 | 121 | # show the diff of the change to the screen 122 | 123 | puts ndev.cu.diff? 124 | 125 | # now rollback the change, since we don't want to save it. 126 | 127 | ndev.cu.rollback! 128 | 129 | ndev.close 130 | ``` 131 | 132 | # PROVIDERS 133 | 134 | Providers manage access to individual resources and their associated properties. Providers/resources exists 135 | for managing life-cycle common changes that you generally need as part of a larger workflow process. For more 136 | documentation on Providers/Resources, see the *docs* directory. 137 | 138 | - L1ports: Physical port management 139 | - L2ports: Ethernet port (VLAN) management 140 | - Vlans: VLAN resource management 141 | - IPports: IP v4 port management 142 | - StaticHosts: Static Hosts [system static-host-mapping ...] 143 | - StaticRoutes: Static Routes [routing-options static ...] 144 | - Group: JUNOS groups management 145 | 146 | # UTILITIES 147 | 148 | - Config: 149 | 150 | These functions allow you to load config snippets, do commit checks, look at config diffs, etc. 151 | Generally speaking, you would want to use the Providers/Resources framework to manage specific 152 | items in the config. This utility library is very useful when doing the initial commissioning 153 | process, where you do not (cannot) model every aspect of Junos. These utilities can also be 154 | used in conjunction with Providers/Resources, specifically around locking/unlocking and committing 155 | the configuration. 156 | 157 | - Filesystem: 158 | 159 | These functions provide you "unix-like" commands that return data in Hash forms rather than 160 | as string output you'd normally have to screen-scraps. These methods include `ls`, `df`, `pwd`, 161 | `cwd`, `cleanup`, and `cleanup!` 162 | 163 | - Routing-Engine: 164 | 165 | These functions provide a general collection to information and functioanlity for handling 166 | routing-engine (RE) processes. These functions `reboot!`, `shutdown!`, `install_software!`, 167 | `ping`. Information gathering such as memory-usage, current users, and RE status information 168 | is also made available through this collection. 169 | 170 | # DEPENDENCIES 171 | 172 | * gem netconf 173 | * Junos OS based products 174 | 175 | # INSTALLATION 176 | 177 | * gem install junos-ez-stdlib 178 | 179 | # CONTRIBUTORS 180 | Juniper Networks is actively contributing to and maintaining this repo. Please contact jnpr-community-netdev@juniper.net 181 | for any queries. 182 | 183 | Contributors: 184 | [John Deatherage](https://github.com/routelastresort), [Nitin Kumar](https://github.com/vnitinv), 185 | [Priyal Jain](https://github.com/jainpriyal), [Ganesh Nalawade](https://github.com/ganeshrn) 186 | 187 | Former Contributors: 188 | [Jeremy Schulman](https://github.com/jeremyschulman) 189 | 190 | # LICENSES 191 | 192 | BSD-2, See LICENSE file 193 | 194 | # SUPPORT 195 | 196 | Support for this software is made available exclusively through Github repo issue tracking. You are also welcome to contact the CONTRIBUTORS directly via their provided contact information. 197 | 198 | If you find a bug, please open an issue against this repo. 199 | 200 | If you have suggestions or ideas, please write them up and add them to the "SUGGESTION-BOX" folder of this repo (via pull request). This way we can share the ideas with the community and crowdsource for feature delivery. 201 | 202 | Thank you! 203 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /SUGGESTION-BOX/README.md: -------------------------------------------------------------------------------- 1 | # PLEASE CONTRIBUTE 2 | 3 | ## IDEAS & SUGGESTIONS 4 | 5 | If you have a suggestion or idea, please write it up as a separate file and do a *pull request*. Your file will be added to this directory as a separate file so folks can peer-review and crowdsource around it. 6 | 7 | It would be helpful to include the following information in the file: 8 | 9 | 1. SYNOPSIS - A brief statement of what is needed 10 | 2. TARGET PLATFORMS - A list of Junos OS based platforms this would apply to 11 | 3. TARGET RELEASE - If you have specific Junos OS release requirements, please state them 12 | 4. TIMELINE - If you have a specific *gotta have it* by date, please let us know 13 | 5. GORY DETAILS - Please provide as much information you can around the use-case or application 14 | 15 | Please name your file in the following format: 16 | 17 | __.md 18 | 19 | For example, let's say I was going to submit something around virtual-routing (VRF), I would name the file: 20 | 21 | 20130430_jeremyschulman_vrf.md 22 | 23 | Thank you all for your support and contributions! 24 | 25 | ## CODE & BUG-FIXES 26 | 27 | Pull requests are welcome and appreciated ! 28 | 29 | # THANK YOU 30 | 31 | This framework is built for the commmunity of DevOps and NetDevOps that need to create automation solutions. Your help and participation in the development of this software is essential 32 | 33 | -------------------------------------------------------------------------------- /docs/Facts.md: -------------------------------------------------------------------------------- 1 | # FACT KEEPING 2 | 3 | This framework is *fact based*, meaning that the provider libraries will have access to information about each 4 | target. Facts enable the framework to abstract the physical differences of the underlying hardware. 5 | 6 | For example,the `Junos::Ez::Vlans::Provider` allows you to manage vlans without having to worry about the differences between the EX product family and the MX product family. To you, the programmer, you simply obtain a resource and manage the associated properties. 7 | 8 | There are collection of standard facts that are always read by the framework. You can find a list of these and the assocaited code in the *libs/../facts* subdirectory. These facts are also avaialble to your program as well. So you can make programmatic decisions based on the facts of the device. 9 | 10 | You can also define your own facts, and then go on to building your own provider libraries (but we're getting ahead of ourselfs here ...) 11 | 12 | # USAGE 13 | 14 | Usage rule: you **MUST** call `Junos::Ez::Provider` on your netconf object: 15 | 16 | - **AFTER** the object has connected to the target, since it will read facts 17 | - **BEFORE** you add any other providers, since these may use the facts 18 | 19 | Here is a basic example: 20 | 21 | ```ruby 22 | require 'pp' 23 | require 'net/netconf/jnpr' 24 | require 'junos-ez/stdlib' 25 | 26 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 27 | 28 | # create a NETCONF object to manage the device 29 | 30 | ndev = Netconf::SSH.new( login ) 31 | 32 | # open the NETCONF connetion, if this fails, the object will throw an exception 33 | ndev.open 34 | 35 | # now that the netconf session has been established, initialize the object for the 36 | # Junos::Ez framework. This will add an instance variable called `@facts` and 37 | # retrieve all known facts from the target 38 | 39 | Junos::Ez::Provider( ndev ) 40 | 41 | # do a quick dump of all facts 42 | 43 | pp ndev.facts.catalog 44 | -> 45 | {:hardwaremodel=>"SRX210H", 46 | :serialnumber=>"AD2909AA0096", 47 | :hostname=>"srx210", 48 | :domain=>"workflowsherpas.com", 49 | :fqdn=>"srx210.workflowsherpas.com", 50 | :RE=> 51 | {:status=>"OK", 52 | :model=>"RE-SRX210H", 53 | :up_time=>"26 days, 15 hours, 46 minutes, 4 seconds", 54 | :last_reboot_reason=>"0x200:normal shutdown"}, 55 | :personality=>:SRX_BRANCH, 56 | :ifd_style=>:CLASSIC, 57 | :switch_style=>:VLAN, 58 | :version=>"12.1X44-D10.4"} 59 | ``` 60 | 61 | # STANDARD FACTS 62 | 63 | The following facts are provided by the `Junos::Ez::Provider` framework: 64 | ``` 65 | :hardwaremodel => String 66 | ``` 67 | Identifies the target hardware model as obtained from the chassis inventory information 68 | ``` 69 | :serialnumber => String 70 | ``` 71 | Identifies the target chassis serial-number as obtained from the chassis inventory information 72 | ``` 73 | :hostname => String 74 | ``` 75 | Identifies the target host-name as obtained from the system configuration 76 | ``` 77 | :domain => String 78 | ``` 79 | Identifies the target domain-name as obtained from the system configuration 80 | ``` 81 | :fqdn => String 82 | ``` 83 | Identifies the target Fully-Qualified-Domain-Name (FQDN), which is the composite of the `:hostname` and `:domain` facts. 84 | ``` 85 | :version => String 86 | ``` 87 | Identifies the Junos version string, e.g. "12.3R2.5" running on the master routing-engine. 88 | ``` 89 | :version_ => String 90 | ``` 91 | When the target is a multi-routing-engine or virtual-chassis system, the version loaded on each control processor is provided as a separate version fact. All version facts begin with `version_`. So and MX router with two routing-engines would have `:version_RE0` and `:version_RE1` in additon to the `:version` fact. An EX vritual chassis with two members would have `:version_FPC0` and `:version_FPC1` facts in additon to the `:version` fact. 92 | ``` 93 | :master => String 94 | ``` 95 | If the target is a multi-routing-engine capabile, this fact will identify the master RE, for example "0". 96 | ``` 97 | :RE => Hash 98 | ``` 99 | For each routing-engine, a Hash structure of information is obtained. 100 | The following illustrates an EX virtual-chassis: 101 | ```ruby 102 | ndev.facts.catalog 103 | -> 104 | {:hardwaremodel=>"Virtual Chassis", 105 | :serialnumber=>"BP0208207236", 106 | :hostname=>"jex", 107 | :domain=>"workflowsherpas.com", 108 | :fqdn=>"jex.workflowsherpas.com", 109 | :RE0=> 110 | {:mastership_state=>"master", 111 | :status=>"OK", 112 | :model=>"EX4200-48T, 8 POE", 113 | :up_time=>"14 minutes, 12 seconds", 114 | :last_reboot_reason=>"0x2:watchdog "}, 115 | :master=>"0", 116 | :RE1=> 117 | {:mastership_state=>"backup", 118 | :status=>"OK", 119 | :model=>"EX4200-48T, 8 POE", 120 | :up_time=>"14 minutes, 12 seconds", 121 | :last_reboot_reason=>"0x2:watchdog "}, 122 | :personality=>:SWITCH, 123 | :ifd_style=>:SWITCH, 124 | :switch_style=>:VLAN, 125 | :version_FPC0=>"12.2R3.5", 126 | :version_FPC1=>"12.2R3.5", 127 | :version_FPC2=>"12.2R3.5", 128 | :version=>"12.2R3.5"} 129 | ``` 130 | Note the presense of`:RE0` and `:RE1` facts, each contains the Hash information for each routing-engine. If a target does not support multiple routing-engnies, then there will be a `:RE` fact (no slot-id). 131 | ``` 132 | :switch_style => [:VLAN, :BRIDGE_DOMAIN, :VLAN_ELS, :NONE] 133 | ``` 134 | Identifies the target style for handing vlan configurations. If the target does not support vlan briding (for example the vSRX), then the style will be set to `:NONE`. 135 | ``` 136 | :personality => [:SWITCH, :MX, :SRX_BRANCH, :SRX_HIGHEND] 137 | ``` 138 | Identifies the personality of the target. 139 | ``` 140 | :ifd_style => [:CLASSIC, :SWITCH] 141 | ``` 142 | Identifies the target style for handling interface configuration differences. 143 | ``` 144 | :virtual => true 145 | ``` 146 | Set if the target is a virtual-machine, like the vSRX 147 | 148 | # METHODS 149 | 150 | - `read!` - reloads the facts from the target 151 | - `facts[]` - retrieve a specific fact from the keeper 152 | - `fact` - alternative method to retrieve a specific fact from the keeper 153 | - `list`, `list!` - returns an Array of fact names (symbols) 154 | - `catalog`, `catalog!` - returns a Hash of fact names and values 155 | 156 | The bang (!) indicates that the method will re-read the value from the target, which the non-bang method uses the values cached in memory. If the cache does not exist, the framework will read the values. The use of the bang-methods are handy if/when you have facts whose values change at runtime, like the `ndev.fact(:RE)[:up_time]` 157 | 158 | # CREATING CUSTOM FACTS 159 | 160 | You can define your own facts using `Junos::Ez::Facts::Keeper.define`. You can review the stdlib facts by looking the *libs/../facts* subdirectory of this repo. Here is the code for the `:chassis` fact. What is interesting about this example, is this code will actually create multiple facts about the chassis. So there is not a required one-to-one relationship between createing a custom fact and the actual number of facts it creates, yo! 161 | 162 | When you define your fact, you must give it a unique "fact name*, and a block. The block takee two arguments: the first is the netconf object, which will provide you access to the underlying Junos XML netconf, an a Hash that allows you to write your facts into the Keeper: 163 | 164 | ```ruby 165 | Junos::Ez::Facts::Keeper.define( :chassis ) do |ndev, facts| 166 | 167 | inv_info = ndev.rpc.get_chassis_inventory 168 | chassis = inv_info.xpath('chassis') 169 | 170 | facts[:hardwaremodel] = chassis.xpath('description').text 171 | facts[:serialnumber] = chassis.xpath('serial-number').text 172 | 173 | cfg = ndev.rpc.get_configuration{|xml| 174 | xml.system { 175 | xml.send(:'host-name') 176 | xml.send(:'domain-name') 177 | } 178 | } 179 | 180 | facts[:hostname] = cfg.xpath('//host-name').text 181 | facts[:domain] = cfg.xpath('//domain-name').text 182 | facts[:fqdn] = facts[:hostname] 183 | facts[:fqdn] += ".#{facts[:domain]}" unless facts[:domain].empty? 184 | 185 | end 186 | ``` 187 | 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /docs/Providers/Group.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::Group::Provider 2 | 3 | Manages JUNOS group properties 4 | 5 | # EXAMPLE 6 | 7 | The provider *name* selector is the JUNOS group name, e.g. "service_group". 8 | 9 | ```ruby 10 | Junos::Ez::Group::Provider( ndev, :group ) 11 | 12 | grp = ndev.group["service_group"] 13 | 14 | grp[:format] = 'set' 15 | grp[:path] = 'services.set' 16 | 17 | grp.write! 18 | 19 | ``` 20 | 21 | # PROPERTIES 22 | 23 | - `:format` - JUNOS configuration format is file. It can be 'xml', 'text' or 'set'. Default is 'xml' 24 | - `:path` - Path of configuration file that is applied inside JUNOS group hierarchy. 25 | 26 | # METHODS 27 | 28 | No additional methods at this time ... 29 | 30 | # USAGE NOTES 31 | 32 | Contents of 'service.set' file 33 | 34 | ```` 35 | % cat services.set 36 | set system services ftp 37 | set system services ssh 38 | set system services netconf ssh 39 | ```` 40 | 41 | JUNOS group configuration reflected on executing above example. 42 | 43 | ```` 44 | {master}[edit] 45 | junos@switch# show groups service_group 46 | system { 47 | services { 48 | ftp; 49 | ssh; 50 | netconf { 51 | ssh; 52 | } 53 | } 54 | } 55 | 56 | junos@switch# show apply-groups 57 | apply-groups [ global re0 re1 service_group ]; 58 | 59 | ```` 60 | 61 | 62 | -------------------------------------------------------------------------------- /docs/Providers/IPports.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::IPports::Provider 2 | 3 | Manages IPv4 ports. For now, ports recognized are: 4 | 5 | - Fast Ethernet: `fe-*` 6 | - Gigabit Ethernet: `ge-*` 7 | - 10 Gigabit Ethernet: `xe-*` 8 | 9 | IPv6 ports are a different provider (comming soon...) 10 | 11 | # USAGE 12 | 13 | The provider *name* selector is the interface. If the *name* does not include the ".0" unit, the framework will default to this. 14 | 15 | ```ruby 16 | Junos::Ez::IPports::Provider( ndev, :ip_ports ) 17 | 18 | port = ndev.ip_ports["ge-0/0/8"] 19 | 20 | puts "IPv4 port #{port.name} does not exist!" unless port.exists? 21 | ``` 22 | 23 | # PROPERTIES 24 | 25 | - `:admin` - [:up, :down] - administrative control of the port 26 | - `:description` - String, description assigned at the interface unit level 27 | - `:tag_id` - Fixnum, used if the phyiscal port is vlan-tagging (but not L2port) 28 | - `:mtu` - Fixnum, MTU value assigned for IP packets (but not L1port MTU) 29 | - `:address` - String in "ip/prefix" format. For example "192.168.10.12/24" 30 | - `:acl_in` - Name of input ACL (firewall-filter) 31 | - `:acl_out` - Name of output ACL 32 | 33 | # METHODS 34 | 35 | ## status 36 | 37 | Returns a Hash of status information about the IP unit interface. 38 | ```ruby 39 | port = ndev.ip_ports["ge-0/0/8.0"] 40 | 41 | # display the configuration information 42 | pp port.to_h 43 | -> 44 | {"ge-0/0/8.0"=> 45 | {:_active=>true, 46 | :_exist=>true, 47 | :admin=>:up, 48 | :description=>"this is port8", 49 | :address=>"192.168.100.1/24", 50 | :acl_in=>"foo", 51 | :acl_out=>"bar"}} 52 | 53 | # display the status information 54 | pp port.status 55 | -> 56 | {:l1_oper_status=>:up, 57 | :oper_status=>:up, 58 | :snmp_index=>522, 59 | :packets_rx=>0, 60 | :packets_tx=>18} 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/Providers/L1ports.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::L1ports::Provider 2 | 3 | Manages the physical properties of interfaces. 4 | 5 | # USAGE 6 | 7 | The provider *name* selector is the interface name. 8 | 9 | ```ruby 10 | Junos::Ez::L1ports::Provider( ndev, :l1_ports ) 11 | 12 | port = ndev.l1_ports["ge-0/0/12"] 13 | 14 | port[:admin] = :down 15 | port.write! 16 | ``` 17 | 18 | # PROPERTIES 19 | 20 | - `:admin` - [:up, :down] - administratively controls the port 21 | - `:description` - String, description applied at the physical port 22 | - `:mtu` - Fixnum, MTU value applied at the physical port 23 | - `:speed` - Link Speed, [:auto, '10m', '100m', '1g', 10g'] 24 | - `:duplex` - Link Duplex, [:auto, :half, :full] 25 | - `:unit_count` - **READ-ONLY** indicates the number of logical ports (units) configured 26 | 27 | # METHODS 28 | 29 | No additional methods at this time ... 30 | -------------------------------------------------------------------------------- /docs/Providers/L2ports.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::L2ports::Provider 2 | 3 | Manages the ethernet switch ports. The primary function is to associate switching ports to VLANs. 4 | 5 | Currently the association of VLANS to ports is read/write under the interfaces stanza. Junos OS also supports 6 | the association under the VLAN resource stanza (vlans/bridge-domains). 7 | 8 | _NOTE: this provider does not use the VLAN resource stanza at this time. Under review now. If you have an opionin on this, please let us know, thank you!_ 9 | 10 | # USAGE 11 | 12 | The provider *name* is the interface. The framework will assume unit 0 if the name does not indicate one. 13 | 14 | ```ruby 15 | Junos::Ez::L2ports::Provider( ndev, l2_ports ) 16 | 17 | port = ndev.l2_ports["ge-0/0/12"] 18 | 19 | puts "port #{port.name} is not a switch-port!" unless port.exists? 20 | ``` 21 | 22 | # PROPERTIES 23 | 24 | - `:description` - String description at the logical interface level 25 | - `:untagged_vlan` - String, VLAN-name for packets without VLAN tags 26 | - `:tagged_vlans` - Set of VLAN-names for packets with VLAN tags 27 | - `:vlan_tagging` - [true | false] - indicates if this port accepts packets with VLAN tags 28 | 29 | # METHODS 30 | 31 | No additional methods at this time ... 32 | 33 | # SUPPORTED PLATFORMS 34 | 35 | - EX2200, EX3200, EX3300, EX4200, EX4500, EX4550, EX6100, EX8200 36 | - SRX branch: **entire product line, but not vSRX** 37 | - QFX3500, QFX3600 38 | 39 | Comming soon: 40 | 41 | - EX platforms released in 2013 42 | - MX5, MX10, MX40, MX80, MX240, MX480, MX960 43 | 44 | -------------------------------------------------------------------------------- /docs/Providers/LAGports.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::LAGports::Provider 2 | 3 | Manages Link Aggregation Group (LAG) port properties 4 | 5 | # EXAMPLE 6 | 7 | The provider *name* selector is the interface name, e.g. "ae0". 8 | 9 | ```ruby 10 | Junos::Ez::LAGports::Provider( ndev, :lags ) 11 | 12 | port = ndev.lags["ae0"] 13 | 14 | port[:links] = ["ge-0/0/0", "ge-0/0/1", "ge-0/0/2", "ge-0/0/3"] 15 | port[:lacp] = :active 16 | port[:minimum_links] = 2 17 | 18 | port.write! 19 | ``` 20 | 21 | # PROPERTIES 22 | 23 | - `:links` - Set of interface names 24 | - `:lacp` - [:active, :passive, :disabled], :disabled is default 25 | - `:minimum_links` - number of interfaces that must be active for LAG to be declared 'up' 26 | 27 | # METHODS 28 | 29 | No additional methods at this time ... 30 | 31 | # USAGE NOTES 32 | 33 | ### Allocating Aggregated Ethernet (AE) Ports in Junos 34 | 35 | Before using LAG ports, you must first configured the "aggregated ethernet ports" device count in Junos. This is done under the `[edit chassis]` stanza as shown: 36 | 37 | ```` 38 | {master:0}[edit chassis] 39 | jeremy@switch# show 40 | aggregated-devices { 41 | ethernet { 42 | device-count 10; 43 | } 44 | } 45 | ```` 46 | 47 | ### Changing the Links Property 48 | 49 | The `:links` property is internally managed as a Ruby Set. When modifing the `:links` property you must use an Array notation, even if you are simply adding or removing one link. For example: 50 | 51 | ````ruby 52 | port = ndev.lags["ae0"] 53 | 54 | port[:links] += ["ge-0/0/15"] 55 | port.write! 56 | ```` 57 | 58 | -------------------------------------------------------------------------------- /docs/Providers/StaticHosts.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::StaticHosts::Provider 2 | 3 | Manages locally configured host-name to IPv4 & IPv6 address mapping 4 | 5 | # USAGE 6 | 7 | The provider *name* is the host-name as it would have been configured under `[edit system static-host-mapping]` 8 | 9 | ```ruby 10 | Junos::Ez::StaticHosts::Provider( ndev, :etc_hosts ) 11 | 12 | host = ndev.etc_hosts["ex4.jeremylab.net"] 13 | host[:ip] = "192.168.10.24" 14 | host.write! 15 | ``` 16 | 17 | # PROPERITES 18 | 19 | - `:ip` - The IPv4 address 20 | - `:ip6` - The IPv6 address 21 | 22 | _NOTE: A host entry **can** have both IPv4 and IPv6 addresses assigned at the same time_ 23 | 24 | # METHODS 25 | 26 | No additional methods at this time ... 27 | -------------------------------------------------------------------------------- /docs/Providers/StaticRoutes.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::StaticRoutes::Provider 2 | 3 | Manages static route entries. 4 | 5 | _NOTE: for now, routing-instances are not supported, but under review for upcoming release..._ 6 | 7 | # USAGE 8 | 9 | The provider *name* is the target-route. If you want to specify the default-route, you can either use "0.0.0.0/0" or the special name `:default`. 10 | 11 | ```ruby 12 | Junos::Ez::StaticRoutes::Provider( ndev, :route ) 13 | 14 | default = ndev.route[:default] 15 | 16 | unless default.exists? 17 | default[:gateway] = "192.168.1.1" 18 | default.write! 19 | end 20 | ``` 21 | 22 | # PROPERTIES 23 | 24 | - `:gateway` - The next-hop gateway. Could be single String or Array-of-Strings 25 | - `:metic` - The metric assigned to this route, Fixnum 26 | - `:action` - Configures the route action, [:reject, :discard, :receive] 27 | - `:active` - Configures the route active, [true, false, nil] 28 | - `:retain` - Configures the ratain/no-retain flag, [ nil, true, false ] 29 | - `:install` - Configures the install/no-install flag, [nil, true, false ] 30 | - `:readvertise` - Configures the readvertise/no-readvertise flag, [nil, true, false] 31 | - `:resovlve` - Configures the resolve/no-resolve falg, [nil, true, false] 32 | 33 | In the above "flag controls", assigning the values [true | false] configures if the flat is set or "no-" set respectively. To delete the flag from the configuration, set the property to `nil`. 34 | 35 | # METHODS 36 | 37 | No additional methods at this time ... 38 | -------------------------------------------------------------------------------- /docs/Providers/UserAuths.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::UserAuths::Provider 2 | 3 | Manages user account ssh-keys, RSA or DSA. 4 | 5 | # USAGE 6 | 7 | The provider *name* for accessing the provider is a Hash comprised of the following key/value pairs: 8 | 9 | - `:user` - String, user-name 10 | - `:keytype` - String, one of ['ssh-rsa', 'ssh-dsa'] 11 | - `:publickey` - String, the public key value 12 | 13 | ```ruby 14 | 15 | # bind :auths for managing ssh-keys directly 16 | 17 | Junos::Ez::UserAuths::Provider( ndev, :auths ) 18 | 19 | # setup a name Hash to access this key 20 | 21 | key_name = {} 22 | key_name[:user] = "jeremy" 23 | key_name[:keytype] = "ssh-rsa" 24 | key_name[:publickey] = "ssh-rsa gibberishMagicSwingDeadCatoverHeadand_LetMeLoginFoo" 25 | 26 | ssh_key = ndev.auths[ key_name ] 27 | 28 | puts "Key does not exist" unless ssh_key.exists? 29 | ``` 30 | 31 | Generally speaking, you probably won't be using this provider directly, but rather using a 32 | `Junos::Ez::Users::Provider` resource and the `load_ssh_key!` method. This method makes use of the `Junos::Ez::UserAuths::Provider` internally. 33 | -------------------------------------------------------------------------------- /docs/Providers/Users.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::Users::Provider 2 | 3 | Manages the on-target configured users, located under Junos `[edit system login]` stanza. 4 | 5 | # USAGE 6 | 7 | The provider *name* selector is the user-name String. 8 | 9 | ```ruby 10 | 11 | # bind :users to provide access to the local login configuration 12 | 13 | Junos::Ez::Users::Provider( ndev, :users ) 14 | 15 | user = ndev.users["jeremy"] 16 | 17 | puts "#{user.name} does not exist!" unless user.exists? 18 | ``` 19 | 20 | # PROPERTIES 21 | 22 | - `:class` - String, The user priviledge class (like "read-only", or "super-user") 23 | - `:uid` - Number, User ID (unix). If not provided, Junos will auto-create 24 | - `:fullname` - String, User Full Name 25 | - `:password` - Junos encrypted password 26 | - `:ssh_keys` - SSH keys (READ/ONLY) 27 | 28 | If you need to modify the user's ssh-keys, see the `load_ssh_key!` method in the next section. 29 | 30 | 31 | 32 | # RESOURCE METHODS 33 | 34 | ## password= 35 | 36 | Used to set the user password by providing a plain-text value. 37 | ```ruby 38 | 39 | Junos::Ez::User::Provider( ndev, :users ) 40 | 41 | pp ndev.users.list 42 | -> 43 | ["goofy", "jeremy"] 44 | 45 | user = ndev.users["goofy"] 46 | user.to_h 47 | -> 48 | {"goofy"=> 49 | {:_active=>true, 50 | :_exist=>true, 51 | :uid=>"3000", 52 | :class=>"read-only", 53 | :password=>"XRykM8Grm0R0A"}} 54 | 55 | # set the password with plaintext value, then re-read the config from the device 56 | user.password = "n3wpassw0rd" 57 | user.read! 58 | 59 | user.to_h 60 | -> 61 | {"goofy"=> 62 | {:_active=>true, 63 | :_exist=>true, 64 | :uid=>"3000", 65 | :class=>"read-only", 66 | :password=>"W05ckLnjLcPCk"}} 67 | ``` 68 | ## load_ssh_key!( :opts = {} ) 69 | 70 | opts[:publickey] - String of public-key 71 | opts[:filename] - String, filename on server to public-key file 72 | 73 | This method will create an ssh-key for the user based on the contents of the provided public key. The key will be written to the device, but not committed (just like resource write!). The `Junos::Ez::UserAuths::Provider` resource for this key will be returned. 74 | 75 | ```ruby 76 | user = ndev.users["jeremy"] 77 | pp user.to_h 78 | -> 79 | {"jeremy"=> 80 | {:_active=>true, 81 | :_exist=>true, 82 | :uid=>"2008", 83 | :class=>"super-user", 84 | :password=>"$1$JhZms6TE$dXF8P1ey1u3G.5j/V9FBk0"}} 85 | 86 | # write the key and then re-load user object 87 | user.load_ssh_key! :filename=>'/home/jschulman/.ssh/keys/key1.pub' 88 | user.read! 89 | pp user.to_h 90 | -> 91 | {"jeremy"=> 92 | {:_active=>true, 93 | :_exist=>true, 94 | :uid=>"2008", 95 | :class=>"super-user", 96 | :password=>"$1$JhZms6TE$dXF8P1ey1u3G.5j/V9FBk0", 97 | :ssh_keys=> 98 | {"ssh-rsa"=> 99 | ["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDIpOXEUJFfHstdDjVEaTIf5YkTbUliSel6/dsNe"]}}} 100 | ``` 101 | ## ssh_key( keytype, index = 0 ) 102 | keytype: ['ssh-rsa', 'ssh-dsa'] 103 | 104 | This method will return a formulate name Hash for the specified key. This name can then be used in conjunction 105 | with the `Junos::Ez::UserAuth::Provider` class. 106 | 107 | The `index` parameter is used to select a key in the event that there is more than one in use. 108 | 109 | ```ruby 110 | key_name = user.ssh_key( 'ssh-rsa' ) 111 | -> 112 | {:user=>"jeremy", 113 | :keytype=>"ssh-rsa", 114 | :publickey=> 115 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDIpOXEUJFfHstdDjVEaTIf5YkTbUliSel6/dsNe"} 116 | 117 | # bind :auths as so we can directly access ssh-keys ... 118 | Junos::Ez::UserAuths::Provider( ndev, :auths ) 119 | 120 | # now delete the key from the user. 121 | ndev.auths[ key_name ].delete! 122 | ``` 123 | -------------------------------------------------------------------------------- /docs/Providers/Vlans.md: -------------------------------------------------------------------------------- 1 | # Junos::Ez::Vlans::Provider 2 | 3 | Manages Ethernet VLANs. 4 | 5 | If you are looking for associating ethernet-ports to VLANs, please refer to the `Junos::Ez::L2ports::Provider` documentation. 6 | 7 | # USAGE 8 | 9 | The provider *name* selector is the vlan-name, String. 10 | 11 | ```ruby 12 | Junos::Ez::Vlans::Provider( ndev, :vlans ) 13 | 14 | vlan = ndev.vlans["Blue"] 15 | 16 | puts "VLAN: #{vlan.name} does not exists!" unless vlan.exists? 17 | ``` 18 | 19 | # PROPERTIES 20 | 21 | - `:vlan_id` - The VLAN tag-id, Fixnum [ 1 .. 4094] 22 | - `:description` - String description for this VLAN 23 | - `:no_mac_learning` - [`:enable`, `:disable`]. If `:enable` this VLAN will not learn MAC addresses 24 | 25 | # RESOURCE METHODS 26 | 27 | ## interfaces 28 | 29 | This method will return a Hash structure of interfaces bound to this VLAN. 30 | ```ruby 31 | ndev.vlans["Green"].interfaces 32 | -> 33 | {"ge-0/0/22"=>{:mode=>:trunk}, 34 | "ge-0/0/0"=>{:mode=>:trunk, :native=>true}, 35 | "ge-0/0/1"=>{:mode=>:trunk, :native=>true}, 36 | "ge-0/0/2"=>{:mode=>:trunk, :native=>true}, 37 | "ge-0/0/3"=>{:mode=>:trunk, :native=>true}, 38 | "ge-0/0/5"=>{:mode=>:trunk, :native=>true}, 39 | "ge-0/0/6"=>{:mode=>:trunk, :native=>true}, 40 | "ge-0/0/7"=>{:mode=>:trunk, :native=>true}, 41 | "ge-0/0/20"=>{:mode=>:access}, 42 | "ge-0/0/21"=>{:mode=>:access}} 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/Providers_Resources.md: -------------------------------------------------------------------------------- 1 | # RESOURCES 2 | 3 | Let's start with Resources before we get to Providers. Resources are generally where all "the action" happens. 4 | The primary purpose of a resource is to allow you to make configuration changes without having to know 5 | the underlying Junos XML configuration. When you have a resource, you can always get a list of the properties 6 | available to you. 7 | 8 | Here's an example of looking at a an ethernet switching port using the L2ports provider. 9 | This is just a snippet of code, and they use the (->) notation to indicate standard output results. 10 | 11 | For the following example assume that `l2_ports` is the provider assigned to the `ndev` object. The `ndev` object is of class `Netconf::SSH` (or has this as a parent class) 12 | 13 | ```ruby 14 | 15 | # select a port by name from the provider 16 | 17 | port = ndev.l2_ports['ge-0/0/0'] 18 | 19 | # check to see if this actually exists in the config 20 | 21 | unless port.exists? 22 | puts "This port #{port.name} does not exist!" 23 | exit 1 24 | end 25 | 26 | # now pretty-print the properties associated with this resource (which would be the same list for 27 | # any L2port resource 28 | 29 | pp port.properties 30 | -> 31 | [:_exist, :_active, :description, :untagged_vlan, :tagged_vlans, :vlan_tagging] 32 | 33 | # now look at the specific values for this resource by pp the assocaite hash 34 | 35 | pp port.to_h 36 | -> 37 | {"ge-0/0/0"=> 38 | {:_active=>true, 39 | :_exist=>true, 40 | :vlan_tagging=>true, 41 | :tagged_vlans=>["Red", "Green", "Blue"], 42 | :untagged_vlan=>nil}} 43 | ``` 44 | 45 | ## Resource Methods 46 | 47 | - `read!` - reads the contents into the resource read-hash (@has) 48 | - `write!` - writes the contents from the resource write-hash (@should) to the device 49 | - `delete!` - delete the resource from the configuration 50 | - `activate!` - activates the configuration for this resource 51 | - `deactivate!` - deactivates the configuration for this resource 52 | - `rename!` - renames the resource in the configuration 53 | - `reorder!` - reorders (Junos `insert`) the resource in the configuration 54 | - `exists?` - indicates if this resource exists in the configuration 55 | - `active?` - indicates if this resource is active in the configuration 56 | - `to_h` - return the read-from and write-to hash values 57 | 58 | ## Instance Variables 59 | 60 | Each resource include a hash structure containing the values read-from the device. This is 61 | the `@has` variable. When modifying properties, the changed values are stored in the 62 | write-to hash `@should` variable. These instance variable are made accessible, but you 63 | should not access them directly. See next section for changing the property values. 64 | 65 | ## Reading Properties 66 | 67 | You can obtain the entire `@has` property hash has using the `to_h` method. This example selects the "ge-0/0/0" physical port and dumps the property hash: 68 | 69 | ```ruby 70 | port = ndev.l1_ports["ge-0/0/1"] 71 | 72 | pp port.to_h 73 | -> 74 | {"ge-0/0/1"=> 75 | {:_active=>true, 76 | :_exist=>true, 77 | :admin=>:up, 78 | :duplex=>:auto, 79 | :speed=>:auto, 80 | :unit_count=>26}} 81 | 82 | ``` 83 | 84 | You can also obtain just a specific property using the `[]` operator: 85 | 86 | ```ruby 87 | pp port[:admin] 88 | -> 89 | :up 90 | ``` 91 | 92 | ## Modifying Properties 93 | 94 | Modifying the resource property is simply making use of the `[]=` operator. For example, 95 | setting the `:untagged_vlan` to "Black" and writing that back to the device would 96 | look something like this: 97 | 98 | ```ruby 99 | port[:untagged_vlan] = "Black" 100 | port.write! 101 | ``` 102 | 103 | You can also obtain the `@should` property hash using the `to_h` method and providing the optional `:write` argument: 104 | 105 | ```ruby 106 | port[:admin] = :down 107 | 108 | pp port.to_h( :write ) 109 | -> 110 | {"ge-0/0/1"=>{:admin=>:down}} 111 | ``` 112 | 113 | _NOTE: The `@should` property hash only contains the changes that will be applied, not every property value._ 114 | 115 | When you execute the `write!` method, the framework will examine the contents of 116 | the `@should` hash to determine what changes need to be made. On success the 117 | values are then transfered into the `@has` hash. 118 | 119 | If you're going to make changes to an array property, you would need to do something like this: 120 | 121 | ```ruby 122 | port[:tagged_vlans] += ["Purple"] 123 | port[:tagged_vlans] -= ["Red", "Green"] 124 | port.write! 125 | ``` 126 | 127 | _NOTE: for Array values, do not use array methods like `delete` as they will operate 128 | on the `@has` hash._ 129 | 130 | ## Special Properties 131 | 132 | All resources include two special properties `_exist` and `_active`. These control whether or 133 | not a resource exists (or should) and whether or not the resource is active (or deactive). 134 | Generally speaking you should not modify these properties directly. Rather you should use the 135 | `delete!` method to remove the resource and the `activate!` and `deactivate!` methods respectively. 136 | That being said, if you have a Hash/YAML dataset that explicity has `:_exist => false` when that 137 | resource is applied to the device, the resource is deleted. This is handy to ensure a specific 138 | resource does not exist, for example. 139 | 140 | # PROVIDERS 141 | 142 | Providers enable access to, and information about resources. So how to you bind a provider to a 143 | Netconf::SSH (netconf) object? This is done by the provider's `Provider` method. There are 144 | two techniques for *bindng* a provider to a netconf object. 145 | 146 | One method is to bind the provider after the call to `Netconf::SSH#open`. For example, if you want 147 | to use the L2port provider, you bind it to the netconf object like so: 148 | 149 | ```ruby 150 | 151 | # creating a netconf object, here login is a hash defining login info 152 | 153 | ndev = Netconf::SSH.new( login ) 154 | 155 | # connect to the target 156 | 157 | ndev.open 158 | 159 | # bind providers to this object 160 | 161 | Junos::Ez::Provider( ndev ) 162 | Junos::Ez::L2ports::Provider( ndev, :l2_ports ) 163 | ``` 164 | 165 | But let's say that you want to create multiple `Netconf::SSH` objects and you don't want to 166 | programmatically do the binding each time? You can define a new class inheriting `Netconf::SSH` and 167 | overload the `open` method, for example: 168 | 169 | ```ruby 170 | class MyJunosSwitch < Netconf::SSH 171 | def open 172 | # must be first to open the connection to the target 173 | super 174 | 175 | # bind init provider, this will retrieve facts 176 | Junos::Ez::Provider( self ) 177 | 178 | # bind other providers you want this object to have 179 | Junos::Ez::L2ports::Provider( self, :l2_ports ) 180 | Junos::Ez::Vlans::Provider( self, :vlans ) 181 | end 182 | end 183 | 184 | # now open a few devices ... 185 | 186 | dev1 = MyJunosSwitch.new( login ) 187 | dev2 = MyJunosSwitch.new( login ) 188 | 189 | dev1.open 190 | dev2.open 191 | 192 | pp dev1.vlans.list 193 | pp dev2.vlans.list 194 | 195 | dev1.close 196 | dev2.close 197 | ``` 198 | 199 | There are a few things to note on these example: 200 | 201 | 1. This framework is built around the NETCONF gem as all of the underlying code access the Junos XML 202 | API via the NETCONF protocol 203 | 204 | 2. You **MUST** use the `Junos::Ez::Provider` before any other providers as this sets up the `Netconf::SSH` 205 | object for future bindings and reads the `facts` from the target. These facts can then be 206 | used by other provider libraries to abstract target specific differences 207 | 208 | 3. **You** get to chose the provider instance variable name (in this case `l2_ports`, there are is no 209 | hard-coding going on in this framework, yo! (except for the `facts` variable) 210 | 211 | ## Listing Providers 212 | 213 | When you bind providers to a netconf object, you can always get a list of what exists: 214 | 215 | ```ruby 216 | pp ndev.providers 217 | -> 218 | [:l1_ports, :ip_ports, :l2_ports] 219 | ``` 220 | 221 | ## Resource List 222 | 223 | You can obtain a list of managed resources using the `list` or `list!` method. This method 224 | will return an Array of names. Again these names could be simple strings or complex values. 225 | The `list!` method causes the framework to re-read from the device. The `list` method uses the cached value. 226 | If there is no-cached value, the framework will read from the device, so you don't need to explicity 227 | use `list!` unless you need to force the cache update. 228 | 229 | ```ruby 230 | pp ndev.l2_ports.list 231 | -> 232 | ["fe-0/0/2", "fe-0/0/3", "fe-0/0/6"] 233 | ``` 234 | 235 | ## Resource Catalog 236 | 237 | You can also obtain the provider's catalog, which is a Hash of resources keyed by name and 238 | each value is the Hash of the associated properties. The `catalog` and `catalog!` methods 239 | work the same as described in the list section above. 240 | 241 | ```ruby 242 | pp ndev.l2_ports.catalog 243 | -> 244 | {"fe-0/0/2"=> 245 | {:_active=>true, 246 | :_exist=>true, 247 | :vlan_tagging=>true, 248 | :tagged_vlans=>["Red", "Green", "Blue"]}, 249 | "fe-0/0/3"=>{:_active=>true, :_exist=>true, :vlan_tagging=>false}, 250 | "fe-0/0/6"=> 251 | {:_active=>true, 252 | :_exist=>true, 253 | :vlan_tagging=>false, 254 | :untagged_vlan=>"Blue"}} 255 | ``` 256 | 257 | ## Selecting a Resource from a Provider 258 | 259 | You select a resource from a provider using the `[]` operator and identifying the resource by *name*. The 260 | name could be a simple string as shown in the previous example, or could be a complex name 261 | like an Array. If you take a look a the SRX library (separate repo), you can see that 262 | the `Junos::Ez::SRX::Policies::Provider` name is an Array of [ from_zone_name, to_zone_name ]. 263 | 264 | Here is an example of selecting an ethernet switching port, "ge-0/0/0": 265 | 266 | ```ruby 267 | port = ndev.l2_ports['ge-0/0/0'] 268 | ``` 269 | 270 | When a resource is selected, the framework will automatically `read!` retrieve the configuration. 271 | 272 | ## Creating a new Resource 273 | 274 | There are two ways to create a resource. One is using the `create` or `create!` methods. 275 | The `create` method is used to create the new resource but not write it do the device. The 276 | `create!` method does both the creation and the write to the device. The `create` method 277 | can also be given a block, so you can setup the contents of the new resource. Here's an 278 | example that creates some config and then deactivates it. 279 | 280 | ```ruby 281 | ndev.l2_ports.create('ge-0/0/20') do |port| 282 | port[:description] = "I am port 20" 283 | port[:untagged_vlan] = "Blue" 284 | port.write! 285 | port.deactivate! 286 | end 287 | ``` 288 | The above example will also return the new resource object. So you could use the Ruby 289 | block as a default initializer, and then continue to make changes to the resource. 290 | 291 | The `create` method takes an optional 2nd argument, a Hash of the resource properties. So you could do the following equivalent: 292 | 293 | ```ruby 294 | data = {:description => "I am port 20", :untagged_vlan => "Blue" } 295 | 296 | ndev.l2_ports.create('ge-0/0/20', data) do |port| 297 | port.write! 298 | port.deactivate! 299 | end 300 | ``` 301 | 302 | The second way is to simply select a resource by name that doesn't exist. So let's 303 | say you want to create a new L2port for `ge-0/0/20`. It would look something like this: 304 | 305 | ```ruby 306 | port = ndev.l2_ports['ge-0/0/20'] 307 | 308 | puts "I don't exist" unless port.exists? 309 | 310 | port[:description] = "I am port 20" 311 | port[:untagged_vlan] = "Storage" 312 | port.write! 313 | ``` 314 | 315 | ### Interating Resources 316 | 317 | You can use the `each` method to iterate through each managed resource, as provided by the `list` provider instance variable. For example: 318 | 319 | ```ruby 320 | ndev.l1_ports.each do |port| 321 | status = port.status 322 | if (port[:admin] == :up) and (status[:oper_status] != :up) 323 | puts "Port #{port.name} should be up, and isn't! 324 | end 325 | end 326 | ``` 327 | 328 | You can use the `with` method to iterate through a given list of managed resources. For example: 329 | 330 | ```ruby 331 | # dump the status for just these two IP ports ... 332 | 333 | ip_list = ["ge-0/0/1.719","ge-0/0/1.335"] 334 | 335 | ndev.ip_ports.with(ip_list) do |ip| 336 | pp ip.name 337 | pp ip.status 338 | end 339 | 340 | -> 341 | "ge-0/0/1.719" 342 | {:l1_oper_status=>:up, 343 | :oper_status=>:down, 344 | :snmp_index=>616, 345 | :packets_rx=>0, 346 | :packets_tx=>0} 347 | "ge-0/0/1.335" 348 | {:l1_oper_status=>:up, 349 | :oper_status=>:up, 350 | :snmp_index=>613, 351 | :packets_rx=>0, 352 | :packets_tx=>0} 353 | ``` 354 | -------------------------------------------------------------------------------- /docs/README_FIRST.md: -------------------------------------------------------------------------------- 1 | # CODE EXAMPLES 2 | 3 | Much of the documentation include small code "snippets". These are not complete programs, but rather meant to show specific functionality. 4 | 5 | The following libraries are assumed to be in scope: 6 | 7 | - `require 'pp'` : for pretty-printing Ruby objects 8 | - `require 'pry'` : for setting code break-points 9 | 10 | These examples use the `->` symbol to indicate screen output. For example: 11 | 12 | ```ruby 13 | 14 | port = ndev.l2_ports["ge-0/0/8"] 15 | pp port.to_h 16 | -> 17 | {"ge-0/0/0"=> 18 | {:_active=>true, 19 | :_exist=>true, 20 | :description=>"Jeremy port for testing", 21 | :vlan_tagging=>true, 22 | :untagged_vlan=>"Green", 23 | :tagged_vlans=>["Red"]}} 24 | 25 | ``` 26 | 27 | Here the Hash structure following the `->` is the output of the prior "pretty-print", `pp port.to_h`, instruction. 28 | -------------------------------------------------------------------------------- /docs/Utils/Config.md: -------------------------------------------------------------------------------- 1 | # `Junos::Ez::Config::Utils` 2 | 3 | A collection of methods to perform file / template based configuration, and configuration control functions, like "commit", "show | compare", "rollback", etc. These methods return data in Hash / Array structures so the information can be programmatically accessible. 4 | 5 | # METHODS 6 | 7 | - [`lock!`](#lock) - attempt exclusive config, returns true or raises Netconf::LockError 8 | - [`load!`](#load) - loads configuration snippets or templates (ERB) 9 | - [`diff?`](#diff) - returns String of "show | compare" as String 10 | - [`commit?`](#commit_check) - checks the candidate config for validation, returns true or Hash of errors 11 | - [`commit!`](#commit) - performs commit, returns true or raises Netconf::CommitError 12 | - [`unlock!`](#unlock) - releases exclusive lock on config 13 | - [`rollback!`](#rollback) - performs rollback of config 14 | - [`get_config`](#get_config) - returns text-format of configuration 15 | 16 | # USAGE 17 | 18 | ```ruby 19 | # bind :cu to give us access to the config utilities 20 | Junos::Ez::Config::Utils( ndev, :cu ) 21 | 22 | # load a Junos configuration file on our local filesys 23 | 24 | ndev.cu.load! :filename => 'basic-setup.conf' 25 | 26 | # check to see if these changes will commit ok. if not, display the errors, rollback the config, 27 | # close the netconf session, and exit the program. 28 | 29 | unless (result = ndev.cu.commit?) == true 30 | puts "There are commit errors, dumping result ..." 31 | pp result 32 | ndev.cu.rollback! 33 | ndev.close 34 | exit 1 35 | end 36 | 37 | # commit the confguration and close the netconf session 38 | 39 | ndev.cu.commit! 40 | ndev.close 41 | ``` 42 | 43 | 44 | 45 | # GORY DETAILS 46 | 47 | ## lock! 48 | Attempt exclusive config, returns `true` if you now have the lock, or raises `Netconf::LockError` exception if the lock is not available 49 | 50 | ## load!( opts = {} ) 51 | 52 | Loads configuration snippets or templates (ERB). This method does **not** commit the change, only loads the contents into the candidate configuration. If the load was successful, this method will return `true`. Otherwise it will raise a `Netconf::EditError` exception. 53 | 54 | The options Hash enables the following controls: 55 | 56 | ``` 57 | :filename => String 58 | ``` 59 | Identifies filename on local-system. File can contain either static config or template in ERB format. The framework will identify the format-style of the content by the filename extension. You can override this behavior using the `:format` option. By default, the framework will map extensions to `:format` as follow: 60 | 61 | - `:text` when *.{conf,text,txt} 62 | - `:set` when *.set 63 | - `:xml` when *.xml 64 | 65 | ``` 66 | :content => String 67 | ``` 68 | Ccontent of configuration, rather than loading it from a file. Handy if you are loading the same content on many devices, and you don't want to keep re-reading it from a file 69 | 70 | ``` 71 | :format => Symbol 72 | ``` 73 | 74 | Identifies the format-style of the configuration. The default is `:text`. Setting this option will override the `:filename` extension style mapping. 75 | 76 | `:text` - indcates "text" or "curly-brace" style 77 | 78 | `:set` - "set" commands, one per line 79 | 80 | `:xml` - native Junos XML 81 | 82 | ``` 83 | :binding => Object | Binding 84 | ``` 85 | Required when the configuration content is a Ruby ERB template. If `:binding` is an Object, then that object becomes the scope of the variables available to the template. If you want to use the *current scope*, then using the `binding` variable that is availble (it is always there) 86 | 87 | ``` 88 | :overwrite! 89 | ``` 90 | When `true` the provided configuraiton will **COMPLETELY OVERWRITE** any existing configuration. This is useful when writing an entire configuration from scratch. 91 | 92 | ``` 93 | :replace! 94 | ``` 95 | When `true` enables the Junos *replace* option. This is required if your configuration changes utilize either the `replace:` statement in text-format style or the `replace="replace"` attribute in XML-format style. You do not need to set this option if you are using the set-format style. 96 | 97 | ## diff? 98 | Returns String of "show | compare" as String. If there is no diff, then this method returns `nil`. 99 | 100 | ## commit? 101 | 102 | Checks the candidate config for validation, returns `true` or Array of errors. 103 | 104 | The following is an example errors: 105 | ```ruby 106 | ndev.cu.commit? 107 | -> 108 | [{:severity=>"error", 109 | :message=>"Referenced filter 'foo' is not defined", 110 | :edit_path=>"[edit interfaces ge-0/0/8 unit 0 family inet]", 111 | :bad_identifier=>"filter"}, 112 | {:severity=>"error", :message=>"configuration check-out failed"}] 113 | ``` 114 | 115 | ## commit!( opts = {} ) 116 | 117 | Performs commit, returns `true` or raises `Netconf::CommitError`. Available options are: 118 | 119 | :comment => String 120 | A commit log comment that is available when retrieving the commit log. 121 | 122 | :confirm => Fixnum-Minutes 123 | Identifies a timeout in minutes to automatically rollback the configuration unless you explicitly issue another commit action. This is very useful if you think your configuration changes may lock you out of the device. 124 | 125 | ## unlock! 126 | 127 | Releases exclusive lock on config. If you do not posses the lock, this method will raise an `Netconf::RpcError` exception. 128 | 129 | ## rollback!( rollback_id = 0 ) 130 | 131 | Loads a rollback of config, does not commit. 132 | 133 | ## get_config( scope = nil ) 134 | 135 | Returns the text-style format of the request config. If `scope` is `nil` then the entire configuration is returned. If the `scope` is invalid (asking for the "foo" stanza for example), then a string with "ERROR!" is returned. If the requested config is non-existant (asking for non-existant interface), then `nil` is returned. 136 | 137 | Successful request: 138 | ```ruby 139 | puts ndev.cu.get_config "interfaces ge-0/0/0" 140 | -> 141 | unit 0 { 142 | family inet { 143 | address 192.168.56.2/24; 144 | } 145 | } 146 | ``` 147 | 148 | Valid request, but not config: 149 | ```ruby 150 | puts ndev.cu.get_config "interfaces ge-0/0/3" 151 | -> 152 | nil 153 | ``` 154 | 155 | Invalid request: 156 | ```ruby 157 | puts ndev.cu.get_config "foober jazzbot" 158 | -> 159 | ERROR! syntax error: foober 160 | ``` 161 | -------------------------------------------------------------------------------- /docs/Utils/Filesystem.md: -------------------------------------------------------------------------------- 1 | # `Junos::Ez::FS::Utils` 2 | 3 | A collection of methods to access filesystem specific functions and information. These methods return data in 4 | Hash / Array structures so the information can be programmatically accessible. 5 | 6 | # METHODS 7 | 8 | - [`cat`](#cat) - returns the String contents of a file 9 | - [`checksum`](#checksum) - returns the checksum of a file (MD5, SHA1, SHA256 options) 10 | - [`cleanup?`](#cleanup_check) - returns a Hash of files that *would be* removed from "request system storage cleanup" 11 | - [`cleanup!`](#cleanup) - "request system storage cleanup" (!! NO CONFIRM !!) 12 | - [`cp!`](#cp) - copies a file relative on the device filesystem 13 | - [`cwd`](#cwd) - changes the current working directory 14 | - [`pwd`](#pwd) - returns a String of the current working directory 15 | - [`df`](#df) - "show system storage" 16 | - [`ls`](#ls) - "file list", i.e. get a file / directory listing, returns a Hash 17 | - [`mv!`](#mv) - "file move", i.e. move / rename files 18 | - [`rm!`](#rm) - "file delete", i.e. deletes files 19 | 20 | # USAGE 21 | ```ruby 22 | 23 | # bind :fs to access the file-system utilities 24 | 25 | Junos::Ez::FS::Utils( ndev, :fs ) 26 | 27 | # get a listing of my home directory files: 28 | 29 | pp ndev.fs.ls '/var/home/jeremy', :detail => true 30 | -> 31 | {"/var/home/jeremy"=> 32 | {:fileblocks=>11244, 33 | :files=> 34 | "key1.pub"=> 35 | {:owner=>"jeremy", 36 | :group=>"staff", 37 | :links=>1, 38 | :size=>405, 39 | :permissions_text=>"-rw-r--r--", 40 | :permissions=>644, 41 | :date=>"Apr 27 15:00", 42 | :date_epoc=>1367074832}, 43 | "template-policy-options.conf"=> 44 | {:owner=>"jeremy", 45 | :group=>"staff", 46 | :links=>1, 47 | :size=>4320, 48 | :permissions_text=>"-rw-r--r--", 49 | :permissions=>644, 50 | :date=>"Nov 6 2011", 51 | :date_epoc=>1320564278}}, 52 | :dirs=> 53 | {".ssh"=> 54 | {:owner=>"jeremy", 55 | :group=>"staff", 56 | :links=>2, 57 | :size=>512, 58 | :permissions_text=>"drwxr-xr-x", 59 | :permissions=>755, 60 | :date=>"Apr 27 19:48", 61 | :date_epoc=>1367092112}, 62 | "bak"=> 63 | {:owner=>"jeremy", 64 | :group=>"staff", 65 | :links=>2, 66 | :size=>512, 67 | :permissions_text=>"drwxr-xr-x", 68 | :permissions=>755, 69 | :date=>"Apr 16 2010", 70 | :date_epoc=>1271441068}}}} 71 | ``` 72 | 73 | 74 | 75 | # GORY DETAILS 76 | 77 | ## `cat( filename )` 78 | Returns the String contents of a file. If the file does not exist, an `IOError` with String error message is raised. 79 | ```ruby 80 | puts ndev.fs.cat '/var/log/messages' 81 | -> 82 | May 2 18:05:32 firefly newsyslog[1845]: logfile turned over due to -F request 83 | 84 | puts ndev.fs.cat 'foober' 85 | exception-> 86 | IOError: "could not resolve file: foober" 87 | ``` 88 | 89 | ## `checksum( method, path )` 90 | Returns the checksum of a file (MD5, SHA1, SHA256 options) located on the Junos target. The `method` idetifies the checksum method, and is one of `[:md5, :sha256, :sha1]`. The `path` argument specifies the file to run the checksum over. If the `path` file does not exist, then an `IOError` exception with String error-message will be raised. 91 | 92 | The following runs an MD5 checksum over the file /var/tmp/junos-vsrx-domestic.tgz located on the Junos target: 93 | ```ruby 94 | ndev.fs.checksum :md5, "/var/tmp/junos-vsrx-domestic.tgz" 95 | -> 96 | "91132caf6030fa88a31c2b9db60ea54d" 97 | 98 | # try to get a checksum on a non-existant file ... 99 | 100 | ndev.fs.checksum :md5, "foober" 101 | exception-> 102 | IOError: "md5: /cf/var/home/jeremy/foober: No such file or directory" 103 | ``` 104 | 105 | ## `cleanup?` 106 | Returns a Hash of files that *would be* removed as a result of the command "request system storage cleanup". 107 | ```ruby 108 | ndev.fs.cleanup? 109 | -> 110 | {"/cf/var/crash/flowd_vsrx.log.firefly.0"=> 111 | {:size_text=>"650B", :size=>650, :date=>"May 3 13:15"}, 112 | "/cf/var/crash/flowd_vsrx.log.firefly.1"=> 113 | {:size_text=>"650B", :size=>650, :date=>"May 3 13:22"}, 114 | "/cf/var/crash/flowd_vsrx.log.firefly.2"=> 115 | {:size_text=>"23B", :size=>23, :date=>"May 5 19:20"}, 116 | "/cf/var/crash/flowd_vsrx.log.firefly.3"=> 117 | {:size_text=>"650B", :size=>650, :date=>"May 5 19:20"}, 118 | "/cf/var/tmp/vpn_tunnel_orig.id"=> 119 | {:size_text=>"0B", :size=>0, :date=>"May 5 19:20"}} 120 | ``` 121 | 122 | ## `cleanup!` 123 | Performs the command "request system storage cleanup" (!! NO CONFIRM !!), and returns a Hash of the files that were removed. 124 | ```ruby 125 | ndev.fs.cleanup! 126 | -> 127 | {"/cf/var/crash/flowd_vsrx.log.firefly.0"=> 128 | {:size_text=>"650B", :size=>650, :date=>"May 3 13:15"}, 129 | "/cf/var/crash/flowd_vsrx.log.firefly.1"=> 130 | {:size_text=>"650B", :size=>650, :date=>"May 3 13:22"}, 131 | "/cf/var/crash/flowd_vsrx.log.firefly.2"=> 132 | {:size_text=>"23B", :size=>23, :date=>"May 5 19:20"}, 133 | "/cf/var/crash/flowd_vsrx.log.firefly.3"=> 134 | {:size_text=>"650B", :size=>650, :date=>"May 5 19:20"}, 135 | "/cf/var/tmp/vpn_tunnel_orig.id"=> 136 | {:size_text=>"0B", :size=>0, :date=>"May 5 19:20"}} 137 | ``` 138 | 139 | ## `cp!( from_file, to_file )` 140 | Copies a file relative on the Junos filesystem. Returns `true` if the operations was successful, raises an `IOError` exceptions with error-message otherwise. 141 | 142 | ```ruby 143 | # copy the vsrx.conf file from the temp directory to the current working directory 144 | ndev.fs.cp! "/var/tmp/vsrx.conf","." 145 | -> 146 | true 147 | 148 | # try to copy a file that doesn't exist 149 | ndev.fs.cp! "/var/tmp/vsrx.conf-bleck","." 150 | (exception)-> 151 | IOError: "File does not exist: /var/tmp/vsrx.conf-bleck 152 | File fetch failed" 153 | ``` 154 | 155 | ## `cwd( directory )` 156 | Changes the current working directory (String). Returns the working directory name if the operation was succesfful. If the requested `directory` does not exist, then an `IOError` with String error-message is raised. 157 | ```ruby 158 | # change to the '/var/tmp' directory. What we see is that this directory is really a symlink to '/cf/var/tmp' 159 | ndev.fs.cwd "/var/tmp" 160 | -> 161 | "/cf/var/tmp" 162 | 163 | # now try to change to a non-existant directory: 164 | 165 | ndev.fs.cwd "/foober" 166 | exception-> 167 | IOError: "invalid directory: /foober" 168 | ``` 169 | 170 | ## `pwd` 171 | Returns a String of the current working directory. 172 | ```ruby 173 | ndev.fs.pwd 174 | -> 175 | "/cf/var/home/jeremy" 176 | ``` 177 | 178 | # `df( opts = {} )` 179 | Returns information about the filesystem storage, gathered from "show system storage". The following options are supported: 180 | ``` 181 | :format => [:hash, :xml, :text] 182 | ``` 183 | Determines the return format, the default is `:hash`. Format `:xml` returns the Junos XML result, and format `:text` returns the CLI text output. 184 | ``` 185 | :size_div => Fixnum 186 | ``` 187 | This option is only valid if `:format => :hash`. When a `:size_div` is provided, this method will change the reported size by dividing it down; handy if you want to covert the size in bytes to something like MB or GB reference. This option will change the values of the `:total_size`, `:used_size`, and `:avail_size` values. 188 | ```ruby 189 | ndev.fs.df 190 | -> 191 | {"/dev/ad0s1a"=> 192 | {:mounted_on=>"/", 193 | :total_blocks=>3313822, 194 | :total_size=>"1.6G", 195 | :used_blocks=>1431770, 196 | :used_size=>"699M", 197 | :used_percent=>47, 198 | :avail_blocks=>1616948, 199 | :avail_size=>"790M"}, 200 | "devfs"=> 201 | {:mounted_on=>"/jail/dev", 202 | :total_blocks=>2, 203 | :total_size=>"1.0K", 204 | :used_blocks=>2, 205 | :used_size=>"1.0K", 206 | :used_percent=>100, 207 | :avail_blocks=>0, 208 | :avail_size=>"0B"}, 209 | # 210 | "/cf/var/log"=> 211 | {:mounted_on=>"/jail/var/log", 212 | :total_blocks=>3313822, 213 | :total_size=>"1.6G", 214 | :used_blocks=>1431770, 215 | :used_size=>"699M", 216 | :used_percent=>47, 217 | :avail_blocks=>1616948, 218 | :avail_size=>"790M"}} 219 | ``` 220 | Same example but dividing down the size by 1024 to put into MB. 221 | ```ruby 222 | [7] pry(main)> ndev.fs.df :size_div => 1024 223 | => {"/dev/ad0s1a"=> 224 | {:mounted_on=>"/", 225 | :total_blocks=>3313822, 226 | :total_size=>1618, 227 | :used_blocks=>1431770, 228 | :used_size=>699, 229 | :used_percent=>47, 230 | :avail_blocks=>1616948, 231 | :avail_size=>789}, 232 | "devfs"=> 233 | {:mounted_on=>"/jail/dev", 234 | :total_blocks=>2, 235 | :total_size=>0, 236 | :used_blocks=>2, 237 | :used_size=>0, 238 | :used_percent=>100, 239 | :avail_blocks=>0, 240 | :avail_size=>0}, 241 | # 242 | "/cf/var/log"=> 243 | {:mounted_on=>"/jail/var/log", 244 | :total_blocks=>3313822, 245 | :total_size=>1618, 246 | :used_blocks=>1431770, 247 | :used_size=>699, 248 | :used_percent=>47, 249 | :avail_blocks=>1616948, 250 | :avail_size=>789}} 251 | ``` 252 | 253 | ## `ls( *args )` 254 | Returns a directory/file listing in a Hash structure. Each primary key is the name of the directory. If the required path is a file, then the key will be an empty string. 255 | The `*args` determine what information is returned. The general format of use is: 256 | ``` 257 | ls , 258 | ``` 259 | Where `path` is a filesystem-path and `options` is a Hash of controls. The following options are supported: 260 | ``` 261 | :format => [:text, :xml, :hash] 262 | ``` 263 | Determines what format this method returns. By default this will be `:hash`. The `:xml` option will return the Junos XML result. The `:text` option will return the CLI text output. 264 | ``` 265 | :recurse => true 266 | ``` 267 | When this option is set, a complete recursive listing will be performed. This is only valid if the `path` is a directory. This option will return full informational detail on the files/directories as well. 268 | ``` 269 | :detail => true 270 | ``` 271 | When this option is set then detailed information, like file size, is provided. 272 | 273 | If no `*args` are passed, then the file listing of the current working directory is provided: 274 | ```ruby 275 | ndev.fs.ls 276 | -> 277 | {"/cf/var/home/jeremy/"=> 278 | {:fileblocks=>7370, 279 | :files=> 280 | {"FF-no-security.conf"=>{}, 281 | "key1.pub"=>{}, 282 | "vsrx.conf"=>{}}, 283 | :dirs=>{".ssh"=>{}}}} 284 | 285 | ``` 286 | Or if you want the details for the current directory listing 287 | ```ruby 288 | [23] pry(main)> ndev.fs.ls :detail=>true 289 | => {"/cf/var/home/jeremy/"=> 290 | {:fileblocks=>7370, 291 | :files=> 292 | {"FF-no-security.conf"=> 293 | {:owner=>"jeremy", 294 | :group=>"staff", 295 | :links=>1, 296 | :size=>366682, 297 | :permissions_text=>"-rw-r--r--", 298 | :permissions=>644, 299 | :date=>"Apr 13 21:56", 300 | :date_epoc=>1365890165}, 301 | "key1.pub"=> 302 | {:owner=>"jeremy", 303 | :group=>"staff", 304 | :links=>1, 305 | :size=>0, 306 | :permissions_text=>"-rw-r--r--", 307 | :permissions=>644, 308 | :date=>"Apr 27 14:59", 309 | :date_epoc=>1367074764}, 310 | "vsrx.conf"=> 311 | {:owner=>"jeremy", 312 | :group=>"staff", 313 | :links=>1, 314 | :size=>1559492, 315 | :permissions_text=>"-rwxr-xr-x", 316 | :permissions=>755, 317 | :date=>"Dec 19 16:27", 318 | :date_epoc=>1355934448}}, 319 | :dirs=> 320 | {".ssh"=> 321 | {:owner=>"jeremy", 322 | :group=>"staff", 323 | :links=>2, 324 | :size=>512, 325 | :permissions_text=>"drwxr-xr-x", 326 | :permissions=>755, 327 | :date=>"Apr 3 14:41", 328 | :date_epoc=>1365000068}}}} 329 | ``` 330 | 331 | ## `mv!( from_path, to_path )` 332 | Move / rename file(s). Returns `true` if the operation was successful, `IOError` exception with String error-message otherwise. 333 | ```ruby 334 | # move the file "vsrx.conf" from the current working directory to the temp directory 335 | ndev.fs.mv! "vsrx.conf","/var/tmp" 336 | -> 337 | true 338 | 339 | # Now do it again to generate an error message[26] pry(main)> ndev.fs.mv! "vsrx.conf","/var/tmp" 340 | ndev.fs.mv! "vsrx.conf","/var/tmp" 341 | exception-> 342 | IOError: 343 | "mv: /cf/var/home/jeremy/vsrx.conf: No such file or directory" 344 | ``` 345 | 346 | ## `rm!( path )` 347 | Removes the file(s) identified by `path`. Returns `true` if the file(s) are removed OK, `IOError` exception with String error-message otherwise. 348 | ```ruby 349 | ndev.fs.rm! "/var/tmp/junos-vsrx-domestic.tgz" 350 | -> 351 | true 352 | 353 | # now try to remove the file again to generate an error .. 354 | ndev.fs.rm! "/var/tmp/junos-vsrx-domestic.tgz" 355 | exception-> 356 | IOError: 357 | "rm: /var/tmp/junos-vsrx-domestic.tgz: No such file or directory" 358 | ``` 359 | 360 | 361 | -------------------------------------------------------------------------------- /docs/Utils/Routing-Engine.md: -------------------------------------------------------------------------------- 1 | # `Junos::Ez::RE::Utils` 2 | 3 | A collection of methods to access routing-engine specific functions and information. These methods return data in Hash / Array structures so the information can be programmatically accessible. 4 | 5 | # METHODS 6 | 7 | ## Informational 8 | 9 | - [`status`](#status) - "show chassis routing-engine" information 10 | - [`uptime`](#uptime) - "show system uptime" information 11 | - [`system_alarms`](#system_alarms) - "show system alarms" information 12 | - [`chassis_alarms`](#chassis_alarms) - "show chassis alarms" information 13 | - [`memory`](#memory) - "show system memory" information 14 | - [`users`](#users) - "show system users" information 15 | 16 | ## Software Image 17 | 18 | - [`software_validate?`](#software_validate) - "request system software validate..." 19 | - [`software_install!`](#software_install) - "request system software add ..." 20 | - [`software_rollback!`](#software_rollback) - "request system software rollback" 21 | - [`software_images`](#software_images) - indicates current/rollback image file names 22 | 23 | ## License Management 24 | 25 | - [`license_install!`](#license_install) - "request system license add" 26 | - [`license_rm!`](#license_rm) - "request system license delete" 27 | - [`licenses`](#licenses) - "show system license" 28 | 29 | ## System Controls 30 | 31 | - [`reboot!`](#reboot) - "request system reboot" (!! NO CONFIRM !!) 32 | - [`shutdown!`](#shutdown) - "request system power-off" (!! NO CONFIRM !!) 33 | 34 | ## Miscellaneous 35 | 36 | - [`ping`](#ping) - Perform a "ping" command 37 | 38 | # USAGE 39 | ```ruby 40 | 41 | # bind :re to access the routing-engine utitities 42 | Junos::Ez::RE::Utils( ndev, :re ) 43 | 44 | # show the uptime information on this device 45 | pp ndev.re.uptime 46 | -> 47 | {"re0"=> 48 | {:time_now=>"2013-04-27 22:28:24 UTC", 49 | :active_users=>1, 50 | :load_avg=>[0.08, 0.05, 0.01], 51 | :uptime=>{:at=>"10:28PM", :ago=>"27 days, 2:58"}, 52 | :time_boot=>{:at=>"2013-03-31 19:30:47 UTC", :ago=>"3w6d 02:57"}, 53 | :protocols_started=>{:at=>"2013-03-31 19:34:53 UTC", :ago=>"3w6d 02:53"}, 54 | :last_config=> 55 | {:at=>"2013-04-27 19:48:42 UTC", :ago=>"02:39:42", :by=>"jeremy"}}} 56 | ``` 57 | 58 | 59 | 60 | # GORY DETAILS 61 | 62 | ## `status` 63 | 64 | Returns a Hash structure of "show chassis routing-engine" information. Each Hash key is the RE identifier. For example, on a target with a single RE: 65 | ```ruby 66 | pp ndev.re.status 67 | -> 68 | {"re0"=> 69 | {:model=>"JUNOSV-FIREFLY RE", 70 | :serialnumber=>"", 71 | :temperature=>{:system=>"", :cpu=>""}, 72 | :memory=>{:total_size=>0, :buffer_util=>0}, 73 | :cpu_util=>{:user=>0, :background=>0, :system=>2, :interrupt=>0, :idle=>98}, 74 | :uptime=> 75 | {:at=>"2013-05-02 17:37:51 UTC", 76 | :ago=>"3 minutes, 4 seconds", 77 | :reboot_reason=>"Router rebooted after a normal shutdown."}, 78 | :load_avg=>[0.06, 0.13, 0.07]}} 79 | ``` 80 | 81 | ## `uptime` 82 | 83 | Returns a Hash structure of "show system uptime" information. Each Hash key is the RE identifier. For example, on a target with a single RE: 84 | ```ruby 85 | pp ndev.re.uptime 86 | -> 87 | {"re0"=> 88 | {:time_now=>"2013-05-02 17:42:09 UTC", 89 | :active_users=>0, 90 | :load_avg=>[0.02, 0.1, 0.06], 91 | :uptime=>{:at=>"5:42PM", :ago=>"4 mins"}, 92 | :time_boot=>{:at=>"2013-05-02 17:37:51 UTC", :ago=>"00:04:18"}, 93 | :protocols_started=>{:at=>"2013-05-02 17:38:08 UTC", :ago=>"00:04:01"}, 94 | :last_config=> 95 | {:at=>"2013-04-27 15:00:55 UTC", :ago=>"5d 02:41", :by=>"root"}}} 96 | ``` 97 | ## `system_alarms` 98 | 99 | Returns an Array of Hash structure of "show system alarms" information. If there are no alarms, this method returns `nil`. For example, a target with a single alarm: 100 | ```ruby 101 | pp ndev.re.system_alarms 102 | -> 103 | [{:at=>"2013-05-02 17:38:03 UTC", 104 | :class=>"Minor", 105 | :description=>"Rescue configuration is not set", 106 | :type=>"Configuration"}] 107 | ``` 108 | 109 | ## `chassis_alarms` 110 | 111 | Returns an Array Hash structure of "show chassis alarms" information. If there are no alarms, this method returns `nil`. For example, a target with no chassis alarms: 112 | ```ruby 113 | pp ndev.re.chassis_alarms 114 | -> 115 | nil 116 | ``` 117 | 118 | ## `memory` 119 | 120 | Returns a Hash structure of "show system memory" information. Each key is the RE indentifier. A target with a single RE would look like the following. Note that the `:procs` Array is the process array, with each element as a Hash of process specific information. 121 | ```ruby 122 | pp ndev.re.memory 123 | -> 124 | {"re0"=> 125 | {:memory_summary=> 126 | {:total=>{:size=>1035668, :percentage=>100}, 127 | :reserved=>{:size=>18688, :percentage=>1}, 128 | :wired=>{:size=>492936, :percentage=>47}, 129 | :active=>{:size=>184152, :percentage=>17}, 130 | :inactive=>{:size=>65192, :percentage=>6}, 131 | :cache=>{:size=>261140, :percentage=>25}, 132 | :free=>{:size=>12660, :percentage=>1}}, 133 | :procs=> 134 | [{:name=>"kernel", 135 | :pid=>0, 136 | :size=>569704, 137 | :size_pct=>54.49, 138 | :resident=>90304, 139 | :resident_pct=>8.71}, 140 | {:name=>"/sbin/pmap", 141 | :pid=>2768, 142 | :size=>4764, 143 | :size_pct=>0.15, 144 | :resident=>1000, 145 | :resident_pct=>0.09}, 146 | {:name=>"file: (mgd) /proc/2766/file (jeremy)", 147 | :pid=>2765, 148 | :size=>727896, 149 | :size_pct=>23.16, 150 | :resident=>18904, 151 | :resident_pct=>1.82}, 152 | # 153 | # snip, omitted full array for sake of sanity ... 154 | # 155 | ]}} 156 | ``` 157 | 158 | ## `users` 159 | 160 | Returns a Array structure of "show system users" information. Each Array item is a Hash structure of user information. A target with a single user logged in would look like: 161 | ```ruby 162 | pp ndev.re.users 163 | -> 164 | [{:name=>"jeremy", 165 | :tty=>"p0", 166 | :from=>"192.168.56.1", 167 | :login_time=>"5:45PM", 168 | :idle_time=>"", 169 | :command=>"-cli (cli)"}] 170 | ``` 171 | 172 | ## `software_images` 173 | Returns a Hash of the currnet and rollback image file-names. 174 | ```ruby 175 | pp ndev.re.software_images 176 | -> 177 | {:rollback=>"junos-12.1I20130415_junos_121_x44_d15.0-576602-domestic", 178 | :current=>"junos-12.1I20130322_2104_slt-builder-domestic"} 179 | ``` 180 | 181 | ## `software_validate?` 182 | 183 | Performs the equivalent of "request system software validate..." and returns `true` if the software passes validation or a String indicating the error message. The following is an example that simply checks for true: 184 | ```ruby 185 | unless ndev.re.software_validate?( file_on_junos ) 186 | puts "The softare does not validate!" 187 | ndev.close 188 | exit 1 189 | end 190 | ``` 191 | 192 | ## `software_install!( opts = {} )` 193 | 194 | Performs the equivalent of "request system software add ..." and returns `true` if the operation was successful or a String indicating the error message. 195 | 196 | The following options are supported: 197 | ``` 198 | :no_validate => true 199 | ``` 200 | Instructs Junos not to validate the software image. You should use this option if your program explicity calls `software_validate?` first, since you don't want to do the validation twice. 201 | ``` 202 | :unlink => true 203 | ``` 204 | Instructs Junos to remove the software package file (.tgz) after the installation has completed. 205 | ``` 206 | :reboot => true 207 | ``` 208 | Instructs Junos to reboot the RE after the software has been installed successfully. 209 | 210 | The following example illustrates an error message: 211 | 212 | ```ruby 213 | puts "Installing image ... please wait ..." 214 | rc = ndev.re.software_install!( :package => file_on_junos, :no_validate => true ) 215 | if rc != true 216 | puts rc 217 | end 218 | ``` 219 | With the results of the `rc` String: 220 | ``` 221 | Verified junos-boot-vsrx-12.1I20130415_junos_121_x44_d15.0-576602.tgz signed by PackageDevelopment_12_1_0 222 | Verified junos-vsrx-12.1I20130415_junos_121_x44_d15.0-576602-domestic signed by PackageDevelopment_12_1_0 223 | 224 | WARNING: The software that is being installed has limited support. 225 | WARNING: Run 'file show /etc/notices/unsupported.txt' for details. 226 | 227 | Available space: -49868 require: 4641 228 | 229 | WARNING: The /cf filesystem is low on free disk space. 230 | WARNING: This package requires 4641k free, but there 231 | WARNING: is only -49868k available. 232 | 233 | WARNING: This installation attempt will be aborted. 234 | WARNING: If you wish to force the installation despite these warnings 235 | WARNING: you may use the 'force' option on the command line. 236 | ERROR: junos-12.1I20130415_junos_121_x44_d15.0-576602-domestic fails requirements check 237 | Installation failed for package '/var/tmp/junos-vsrx-domestic.tgz' 238 | WARNING: Not enough space in /var/tmp to unpack junos-12.1I20130415_junos_121_x44_d15.0-576602.tgz 239 | WARNING: Use 'request system storage cleanup' and 240 | WARNING: the 'unlink' option to improve the chances of success 241 | ``` 242 | 243 | ## `software_rollback!` 244 | 245 | Performs the equivalent of "request system software rollback". The result of the operation is returned as a String. For example, a successful rollback would look like this: 246 | ```ruby 247 | pp ndev.re.software_rollback! 248 | -> 249 | "Restoring boot file package\njunos-12.1I20130415_junos_121_x44_d15.0-576602-domestic will become active at next reboot\nWARNING: A reboot is required to load this software correctly\nWARNING: Use the 'request system reboot' command\nWARNING: when software installation is complete" 250 | ``` 251 | An unsuccessful rollback would look like this: 252 | ```ruby 253 | pp ndev.re.software_rollback! 254 | -> 255 | "WARNING: Cannot rollback, /packages/junos is not valid" 256 | ``` 257 | 258 | ## `reboot!( opts = {} )` 259 | Performs the "request system reboot" action. There is **NO** confirmation prompt, so once you've executed this method, the action begins. Once this command executes the NETCONF session to the target will eventually terminate. You can trap the `Net::SSH::Disconnect` exception to detect this event. 260 | 261 | The option Hash provides for the following controls: 262 | ``` 263 | :in => Fixnum 264 | ``` 265 | Instructs Junos to reboot after `:in` minutes from the time of calling `reboot!` 266 | ``` 267 | :at => String 268 | ``` 269 | Instructs Junos to reboot at a specific date and time. The format of `:at` is YYYYMMDDHHMM, where HH is the 24-hour (military) time. For example HH = 01 is 1am and HH=13 is 1pm. If you omit the YYYY, MM, or DD options the current values apply. For example `:at => 1730` is 1:30pm today. 270 | 271 | ## `shutdown!( opts = {} )` 272 | 273 | Performs the "request system power-off" action. There is **NO** confirmation prompt, so once you've executed this method, the action begins. Once this command executes the NETCONF session to the target will eventually terminate. You can trap the `Net::SSH::Disconnect` exception to detect this event. 274 | 275 | The option Hash provides for the following controls: 276 | ``` 277 | :in => Fixnum 278 | ``` 279 | Instructs Junos to reboot after `:in` minutes from the time of calling `reboot!` 280 | ``` 281 | :at => String 282 | ``` 283 | Instructs Junos to reboot at a specific date and time. The format of `:at` is YYYYMMDDHHMM, where HH is the 24-hour (military) time. For example HH = 01 is 1am and HH=13 is 1pm. If you omit the YYYY, MM, or DD options the current values apply. For example `:at => 1730` is 1:30pm today. 284 | 285 | ## `license_install!( opts = {} )` 286 | Installs the provided license. This method will return `true` if the key is installed correctly or a String message indicating the error. 287 | 288 | The following options are supported, you **MUST** use either `:key` or `:filename` to provide the license ASCII-text. 289 | ``` 290 | :key 291 | ``` 292 | The ASCII-text of the key. 293 | ``` 294 | :filename 295 | ``` 296 | The path to the file on the server (not Junos) that contains the ASCII-text of the key. 297 | 298 | The following illustates how to load a key from the server filesystem. 299 | ```ruby 300 | ndev.re.license_install! :filename=>'/cygwin/home/jschulman/license.txt' 301 | -> 302 | true 303 | ``` 304 | ## `license_rm!( license_id )` 305 | Removes either a specific license or `:all` licenses from the target. This method will return `true` if the action was successful, or a String error-message otherwise. 306 | 307 | Removing a specific license: 308 | ```ruby 309 | ndev.re.license_rm! "JUNOS410496" 310 | -> 311 | true 312 | ``` 313 | Removing all licenses 314 | ```ruby 315 | ndev.re.license_rm! :all 316 | -> 317 | true 318 | ``` 319 | 320 | ## `licenses( opts = {} )` 321 | 322 | Returns a Hash structure of information gathered from the "show system license" command. 323 | 324 | The following options are supported: 325 | ``` 326 | :keys => true 327 | ``` 328 | Returns the license key value in ASCII text format. 329 | 330 | Without the `:keys` option: 331 | 332 | ```ruby 333 | pp ndev.re.licenses 334 | -> 335 | {"JUNOS410496"=> 336 | {:state=>"valid", 337 | :version=>"2", 338 | :serialnumber=>"91730A00092074", 339 | :customer=>"LABVSRXJuniper-SEs", 340 | :features=> 341 | {"all"=> 342 | {:description=>"All features", 343 | :date_start=>"2013-02-05", 344 | :date_end=>"2014-02-06"}}}} 345 | ``` 346 | With the `:keys` option: 347 | ```ruby 348 | pp ndev.re.licenses :keys=>true 349 | -> 350 | {"JUNOS410496"=> 351 | {:state=>"valid", 352 | :version=>"2", 353 | :serialnumber=>"91730A00092074", 354 | :customer=>"LABVSRXJuniper-SEs", 355 | :features=> 356 | {"all"=> 357 | {:description=>"All features", 358 | :date_start=>"2013-02-05", 359 | :date_end=>"2014-02-06"}}, 360 | :key=> 361 | "\nJUNOS410496 aeaqec agaia3 27n65m fq4ojr g4ztaq jqgayd\n smrqg4 2aye2m ifbfmu DEADBEF k3tjob sxelkt\n "}} 362 | ``` 363 | 364 | ## `ping( host, opts = {} )` 365 | 366 | Issues a 'ping' from the Junos target, very handy for troubleshooting. This method will return `true` if the ping action was successful, or `false` otherwise. 367 | 368 | The following options are supported, and they are the same as documented by the Junos techpubs: 369 | ``` 370 | :do_not_fragment, :inet, :inet6, :strict, 371 | :count, :interface, :interval, :mac_address, 372 | :routing_instance, :size, :source, :tos, :ttl, :wait 373 | ``` 374 | Here is a ping example that uses the 'do-no-fragment' and 'count' options: 375 | ```ruby 376 | ndev.re.ping "192.168.56.1", :count => 5, :do_not_fragment => true 377 | -> 378 | true 379 | ``` 380 | -------------------------------------------------------------------------------- /docs/Utils/SCP.md: -------------------------------------------------------------------------------- 1 | # `Net::SCP` 2 | 3 | The NETCONF object already provides a mechanism to use secure-copy (SCP), so technically this is _not_ part of the `Junos::EZ` framework. That being said, this is some quick documentation and URLs so you can copy files to and from Junos devices. 4 | 5 | # DOCS 6 | 7 | For documentation on `Net::SCP`, please refer to this website: http://net-ssh.github.io/scp/v1/api/index.html 8 | 9 | # USAGE 10 | 11 | The NETCONF object includes an instance variable `scp` that is class `Net::SCP`. 12 | 13 | To copy a file from the server to the Junos target, you use the `upload` or `upload!` method. The bang(!) version is blocking, meaning the code execution will resume only after the file has been completely transfered. 14 | 15 | ```ruby 16 | file_on_server = "/cygwin/junos/junos-ex4200-image.tgz" 17 | location_on_junos = "/var/tmp" 18 | 19 | ndev.scp.upload!( from_on_server, location_on_junos ) 20 | ``` 21 | 22 | To copy a file from the Junos target to the server, you use the `download` or `download!` method. 23 | 24 | Both upload and download methods can take a Ruby block which is used to provide progress updates on the transfer. There is a complete "software upgrade" example that illustrates this technique in the _examples_ directory. 25 | -------------------------------------------------------------------------------- /examples/config/config_file.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | This exmaple illustrates the use of the Junos::Ez::Config::Utils library. The code will load the contents 4 | of a configuration file and save the diff output to the file "diffs.txt". Error checking/exception 5 | handling is also demonstrated. If there are any errors, the "pretty-print" (pp) function will dump the 6 | contents of the result structure to stderr so you can see what it looks like. 7 | 8 | =end 9 | 10 | require 'net/netconf/jnpr' 11 | require 'junos-ez/stdlib' 12 | 13 | # login information for NETCONF session 14 | 15 | login = { :target => 'vsrx', :username => 'jeremy', :password => 'jeremy1', } 16 | 17 | ## create a NETCONF object to manage the device and open the connection ... 18 | 19 | ndev = Netconf::SSH.new( login ) 20 | $stdout.print "Connecting to device #{login[:target]} ... " 21 | ndev.open 22 | $stdout.puts "OK!" 23 | 24 | # attach the junos-ez objects to the ndev object ... 25 | 26 | Junos::Ez::Provider( ndev ) 27 | Junos::Ez::Config::Utils( ndev, :cfg ) 28 | 29 | # begin a block to trap any raised expections ... 30 | begin 31 | 32 | # lock the candidate config 33 | ndev.cfg.lock! 34 | 35 | # load the contents of the 'load_sample.conf' file 36 | # into the device. 37 | 38 | $stdout.puts "Loading changes ..." 39 | ndev.cfg.load! :filename => 'load_sample.conf' 40 | 41 | # check to see if commit-check passes. if it doesn't 42 | # it will return a structure of errors 43 | 44 | unless (errs = ndev.cfg.commit?) == true 45 | $stderr.puts "Commit check failed" 46 | pp errs 47 | ndev.close # will auto-rollback changes 48 | exit 1 49 | end 50 | 51 | # save the cnfig diff to a file ... 52 | File.open( "diffs.txt", "w") {|f| f.write ndev.cfg.diff? } 53 | 54 | # commit the changes and unlock the config 55 | 56 | $stdout.puts "Commiting changes ..." 57 | ndev.cfg.commit! 58 | ndev.cfg.unlock! 59 | 60 | $stdout.puts "Done!" 61 | 62 | rescue Netconf::LockError 63 | $stderr.puts "Unable to lock config" 64 | rescue Netconf::EditError => e 65 | $stderr.puts "Unable to load configuration" 66 | pp Junos::Ez::rpc_errors( e.rsp ) 67 | rescue Netconf::CommitError => e 68 | $stderr.puts "Unable to commit configuration" 69 | pp Junos::Ez::rpc_errors( e.rpc ) 70 | end 71 | 72 | ndev.close 73 | -------------------------------------------------------------------------------- /examples/config/config_template_object.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | This exmaple illustrates the use of the Junos::Ez::Config::Utils library. The code will load the contents 4 | of a template (ERB) file and use an object to contain the variables used in scope (binding). 5 | 6 | Error checking/exception handling is also demonstrated. If there are any errors, the "pretty-print" 7 | (pp) function will dump the contents of the result structure to stderr so you can see what it looks like. 8 | 9 | =end 10 | 11 | require 'net/netconf/jnpr' 12 | require 'junos-ez/stdlib' 13 | 14 | # login information for NETCONF session 15 | 16 | login = { :target => 'vsrx', :username => 'jeremy', :password => 'jeremy1', } 17 | 18 | ## create a NETCONF object to manage the device and open the connection ... 19 | 20 | ndev = Netconf::SSH.new( login ) 21 | $stdout.print "Connecting to device #{login[:target]} ... " 22 | ndev.open 23 | $stdout.puts "OK!" 24 | 25 | # attach the junos-ez objects to the ndev object ... 26 | 27 | Junos::Ez::Provider( ndev ) 28 | Junos::Ez::Config::Utils( ndev, :cfg ) 29 | 30 | # begin a block to trap any raised expections ... 31 | begin 32 | 33 | # lock the candidate config 34 | ndev.cfg.lock! 35 | 36 | # load the template (ERB) file and use an object to contains the variables 37 | # in scope durning the ERB result evaluation, so declare that now just for demo purposes ... 38 | 39 | class MyClass 40 | def initialize 41 | @interfaces = ['ge-0/0/1','ge-0/0/2','ge-0/0/3'] 42 | end 43 | end 44 | 45 | myobj = MyClass.new 46 | 47 | $stdout.puts "Loading changes ..." 48 | ndev.cfg.load! :filename => 'load_template_object.conf', :binding => myobj 49 | 50 | # check to see if commit-check passes. if it doesn't 51 | # it will return a structure of errors 52 | 53 | unless (errs = ndev.cfg.commit?) == true 54 | $stderr.puts "Commit check failed" 55 | pp errs 56 | ndev.close # will auto-rollback changes 57 | exit 1 58 | end 59 | 60 | # save the cnfig diff to a file ... 61 | File.open( "diffs.txt", "w") {|f| f.write ndev.cfg.diff? } 62 | 63 | # commit the changes and unlock the config 64 | 65 | $stdout.puts "Commiting changes ..." 66 | ndev.cfg.commit! 67 | ndev.cfg.unlock! 68 | 69 | $stdout.puts "Done!" 70 | 71 | rescue Netconf::LockError 72 | $stderr.puts "Unable to lock config" 73 | rescue Netconf::EditError => e 74 | $stderr.puts "Unable to load configuration" 75 | pp Junos::Ez::rpc_errors( e.rsp ) 76 | rescue Netconf::CommitError => e 77 | $stderr.puts "Unable to commit configuration" 78 | pp Junos::Ez::rpc_errors( e.rpc ) 79 | end 80 | 81 | ndev.close 82 | -------------------------------------------------------------------------------- /examples/config/config_template_simple.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | This exmaple illustrates the use of the Junos::Ez::Config::Utils library. The code will load the contents 4 | of a template (ERB) file and use the current variables in the main scope (binding) to fill in the details. 5 | 6 | Error checking/exception handling is also demonstrated. If there are any errors, the "pretty-print" 7 | (pp) function will dump the contents of the result structure to stderr so you can see what it looks like. 8 | 9 | =end 10 | 11 | require 'net/netconf/jnpr' 12 | require 'junos-ez/stdlib' 13 | 14 | # login information for NETCONF session 15 | 16 | login = { :target => 'vsrx', :username => 'jeremy', :password => 'jeremy1', } 17 | 18 | ## create a NETCONF object to manage the device and open the connection ... 19 | 20 | ndev = Netconf::SSH.new( login ) 21 | $stdout.print "Connecting to device #{login[:target]} ... " 22 | ndev.open 23 | $stdout.puts "OK!" 24 | 25 | # attach the junos-ez objects to the ndev object ... 26 | 27 | Junos::Ez::Provider( ndev ) 28 | Junos::Ez::Config::Utils( ndev, :cfg ) 29 | 30 | # begin a block to trap any raised expections ... 31 | begin 32 | 33 | # lock the candidate config 34 | ndev.cfg.lock! 35 | 36 | # load the template (ERB) file and use the current variables in scope (binding) 37 | # when evaluating the results of the template. The template references a 38 | # veriable called 'interfaces', so declare that now ... 39 | 40 | interfaces = ['ge-0/0/1','ge-0/0/2','ge-0/0/3'] 41 | 42 | $stdout.puts "Loading changes ..." 43 | ndev.cfg.load! :filename => 'load_template_main.conf', :binding => binding 44 | 45 | # check to see if commit-check passes. if it doesn't 46 | # it will return a structure of errors 47 | 48 | unless (errs = ndev.cfg.commit?) == true 49 | $stderr.puts "Commit check failed" 50 | pp errs 51 | ndev.close # will auto-rollback changes 52 | exit 1 53 | end 54 | 55 | # save the cnfig diff to a file ... 56 | File.open( "diffs.txt", "w") {|f| f.write ndev.cfg.diff? } 57 | 58 | # commit the changes and unlock the config 59 | 60 | $stdout.puts "Commiting changes ..." 61 | ndev.cfg.commit! 62 | ndev.cfg.unlock! 63 | 64 | $stdout.puts "Done!" 65 | 66 | rescue Netconf::LockError 67 | $stderr.puts "Unable to lock config" 68 | rescue Netconf::EditError => e 69 | $stderr.puts "Unable to load configuration" 70 | pp Junos::Ez::rpc_errors( e.rsp ) 71 | rescue Netconf::CommitError => e 72 | $stderr.puts "Unable to commit configuration" 73 | pp Junos::Ez::rpc_errors( e.rpc ) 74 | end 75 | 76 | ndev.close 77 | -------------------------------------------------------------------------------- /examples/config/load_sample.conf: -------------------------------------------------------------------------------- 1 | # sample contributed by "Maarten at the Amsterdam University of Applied Sciences", @289Sec 2 | # slight mods by @nwkautomaniac 3 | 4 | # Prefix-lists: 5 | policy-options { 6 | prefix-list dns-servers-ipv4 { 7 | apply-path "system name-server <*.*>"; 8 | } 9 | prefix-list ntp-servers-ipv4 { 10 | apply-path "system ntp server <*.*>"; 11 | } 12 | prefix-list snmp-client-systems-ipv4 { 13 | apply-path "snmp client-list <*> <*.*>"; 14 | } 15 | prefix-list tacacs-servers-ipv4 { 16 | apply-path "system tacplus-server <*.*>"; 17 | } 18 | prefix-list radius-servers-ipv4 { 19 | apply-path "access radius-server <*.*>"; 20 | } 21 | prefix-list management-networks-ipv4 { 22 | 172.20.0.0/16; 23 | 192.168.56.0/24; 24 | } 25 | } 26 | 27 | 28 | # Firewall filter: 29 | firewall { 30 | family inet { 31 | filter re-protect-ipv4 { 32 | term discard-fragments-icmp { 33 | from { 34 | is-fragment; 35 | protocol icmp; 36 | } 37 | then discard; 38 | } 39 | term icmp-allow { 40 | from { 41 | protocol icmp; 42 | icmp-type [ echo-request echo-reply unreachable time-exceeded source-quench ]; 43 | } 44 | then accept; 45 | } 46 | term dns-allow { 47 | from { 48 | source-prefix-list { 49 | dns-servers-ipv4; 50 | } 51 | protocol [ udp tcp ] 52 | source-port domain; 53 | } 54 | then accept; 55 | } 56 | term ntp-allow { 57 | from { 58 | source-prefix-list { 59 | ntp-servers-ipv4; 60 | } 61 | protocol udp; 62 | source-port ntp; 63 | } 64 | then accept; 65 | } 66 | term snmp-allow { 67 | from { 68 | source-prefix-list { 69 | snmp-client-systems-ipv4; 70 | } 71 | protocol udp; 72 | destination-port snmp; 73 | } 74 | then accept; 75 | } 76 | term tacacs-allow { 77 | from { 78 | source-prefix-list { 79 | tacacs-servers-ipv4; 80 | } 81 | protocol tcp; 82 | source-port tacacs; 83 | } 84 | then accept; 85 | } 86 | term radius-allow { 87 | from { 88 | source-prefix-list { 89 | radius-servers-ipv4; 90 | } 91 | protocol udp; 92 | source-port radius; 93 | } 94 | then accept; 95 | } 96 | term ssh-allow { 97 | from { 98 | source-prefix-list { 99 | management-networks-ipv4; 100 | } 101 | protocol tcp; 102 | destination-port ssh; 103 | } 104 | then { 105 | accept; 106 | } 107 | } 108 | term everything-else-discard { 109 | then { 110 | discard; 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | 118 | # Interface configuration: 119 | interfaces { 120 | fe-0/0/0 { 121 | unit 0 { 122 | family inet { 123 | filter { 124 | input re-protect-ipv4; 125 | } 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /examples/config/load_sample.set: -------------------------------------------------------------------------------- 1 | set system host-name jeremy 2 | set system domain-name foo.bar 3 | 4 | -------------------------------------------------------------------------------- /examples/config/load_template_main.conf: -------------------------------------------------------------------------------- 1 | interfaces { 2 | <% interfaces.each do |ifd| %> 3 | <%= ifd %> { 4 | disable; 5 | } 6 | <% end %> 7 | } 8 | -------------------------------------------------------------------------------- /examples/config/load_template_object.conf: -------------------------------------------------------------------------------- 1 | interfaces { 2 | <% @interfaces.each do |ifd| %> 3 | <%= ifd %> { 4 | disable; 5 | } 6 | <% end %> 7 | } 8 | -------------------------------------------------------------------------------- /examples/config/multi_config.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'net/netconf/jnpr' 3 | require 'junos-ez/stdlib' 4 | 5 | # list of targets to make the change. Hardcoding these as array 6 | # but you could load them from a file, etc. 7 | 8 | targets = [ 'vsrx', 'ex-10', 'ex-20', 'ex-33' ] 9 | 10 | # let's assume that all targets use the same login/password ... 11 | 12 | login = { :username => 'jeremy', :password => 'jeremy1', } 13 | 14 | 15 | # ------------------------------------------------------------------- 16 | # define a function to do the configuration actions 17 | # ------------------------------------------------------------------- 18 | 19 | def load_stuff( login ) 20 | 21 | ## create a NETCONF object to manage the device and open the connection ... 22 | ndev = Netconf::SSH.new( login ) 23 | $stdout.print "Connecting to device #{login[:target]} ... " 24 | ndev.open 25 | $stdout.puts "OK!" 26 | 27 | Junos::Ez::Provider( ndev ) 28 | Junos::Ez::Config::Utils( ndev, :cfg ) 29 | 30 | # lock the candidate config 31 | # ndev.cfg.lock! 32 | 33 | # examples of loading ... 34 | # ndev.cfg.load! :filename => 'load_sample.conf' 35 | # ndev.cfg.load! :content => File.read( 'load_sample.conf' ), :format => :text 36 | # ndev.cfg.load! :filename => 'load_sample.set' 37 | 38 | binding.pry 39 | 40 | # check to see if the config is OK to commit 41 | # ndev.cfg.commit? 42 | 43 | # perform the commit 44 | # ndev.cfg.commit! 45 | 46 | # unlock the config 47 | # ndev.cfg.unlock! 48 | 49 | ndev.close 50 | end 51 | 52 | ### ----------------------------------------------------------------- 53 | ### run through each of the target names and load configs .. 54 | ### ----------------------------------------------------------------- 55 | 56 | targets.each do |target| 57 | login[:target] = target 58 | load_stuff( login ) 59 | end 60 | 61 | -------------------------------------------------------------------------------- /examples/fs_utils.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'pp' 3 | require 'yaml' 4 | require 'net/netconf/jnpr' 5 | require 'junos-ez/stdlib' 6 | 7 | # login information for NETCONF session 8 | 9 | login = { :target => 'vsrx', :username => 'jeremy', :password => 'jeremy1', } 10 | 11 | ## create a NETCONF object to manage the device and open the connection ... 12 | 13 | ndev = Netconf::SSH.new( login ) 14 | $stdout.print "Connecting to device #{login[:target]} ... " 15 | ndev.open 16 | $stdout.puts "OK!" 17 | 18 | ## Now bind providers to the device object. 19 | ## the 'Junos::Ez::Provider' must be first before all others 20 | ## this provider will setup the device 'facts'. The other providers 21 | ## allow you to define the instance variables; so this example 22 | ## is using 'l1_ports' and 'ip_ports', but you could name them 23 | ## what you like, yo! 24 | 25 | Junos::Ez::Provider( ndev ) 26 | Junos::Ez::Fs::Utils( ndev, :fs ) 27 | 28 | 29 | binding.pry 30 | 31 | ndev.close 32 | -------------------------------------------------------------------------------- /examples/lag_port.rb: -------------------------------------------------------------------------------- 1 | require 'net/netconf/jnpr' 2 | require 'junos-ez/stdlib' 3 | 4 | unless ARGV[0] 5 | puts "You must specify a target" 6 | exit 1 7 | end 8 | 9 | # login information for NETCONF session 10 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 11 | 12 | ## create a NETCONF object to manage the device and open the connection ... 13 | 14 | ndev = Netconf::SSH.new( login ) 15 | $stdout.print "Connecting to device #{login[:target]} ... " 16 | ndev.open 17 | $stdout.puts "OK!" 18 | 19 | Junos::Ez::Provider( ndev ) 20 | Junos::Ez::Config::Utils( ndev, :cu ) 21 | Junos::Ez::LAGports::Provider( ndev, :lags ) 22 | Junos::Ez::Vlans::Provider( ndev, :vlans ) 23 | Junos::Ez::L2ports::Provider( ndev, :l2_ports ) 24 | 25 | binding.pry 26 | 27 | ndev.close 28 | -------------------------------------------------------------------------------- /examples/re_upgrade.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'pp' 3 | require 'net/scp' 4 | require 'net/netconf/jnpr' 5 | require 'junos-ez/stdlib' 6 | 7 | unless ARGV[0] 8 | puts "You must specify a target" 9 | exit 1 10 | end 11 | 12 | # login information for NETCONF session 13 | 14 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 15 | 16 | ## create a NETCONF object to manage the device and open the connection ... 17 | 18 | ndev = Netconf::SSH.new( login ) 19 | print "Connecting to device #{login[:target]} ... " 20 | ndev.open 21 | puts "OK!" 22 | 23 | ## attach our private & utils that we need ... 24 | 25 | Junos::Ez::Provider( ndev ) 26 | Junos::Ez::RE::Utils( ndev, :re ) # routing-engine utils 27 | Junos::Ez::FS::Utils( ndev, :fs ) # filesystem utils 28 | 29 | ## upload the software image to the target device 30 | ## http://net-ssh.github.io/net-scp/ 31 | 32 | file_name = 'junos-vsrx-domestic.tgz' 33 | file_on_server = '/home/jschulman/junos-images/vsrx/' + file_name 34 | file_on_junos = '/var/tmp/' + file_name 35 | 36 | ## simple function to use the Netconf::SSH SCP functionality to 37 | ## upload a file to the device and watch the percetage tick by ... 38 | 39 | def copy_file_to_junos( ndev, file_on_server, file_on_junos ) 40 | mgr_i = cur_i = 0 41 | backitup = 8.chr * 4 42 | ndev.scp.upload!( file_on_server, file_on_junos ) do |ch, name, sent, total| 43 | pct = (sent.to_f / total.to_f) * 100 44 | mgr_i = pct.to_i 45 | if mgr_i != cur_i 46 | cur_i = mgr_i 47 | printf "%4s", cur_i.to_s + "%" 48 | print backitup 49 | end 50 | end 51 | end 52 | 53 | print "Copying file to Junos ... " 54 | copy_file_to_junos( ndev, file_on_server, file_on_junos ) 55 | puts "OK! " 56 | 57 | ### 58 | ### check the MD5 checksum values 59 | ### 60 | 61 | print "Validating MD5 checksum ... " 62 | md5_on_s = Digest::MD5.file( file_on_server ).to_s 63 | md5_on_j = ndev.fs.checksum( :md5, file_on_junos ) 64 | 65 | if md5_on_s != md5_on_j 66 | puts "The MD5 checksum values do not match!" 67 | ndev.close 68 | exit 1 69 | end 70 | 71 | puts "OK!" 72 | 73 | print "Validating image ... please wait ... " 74 | 75 | unless ndev.re.software_validate?( file_on_junos ) 76 | puts "The softare does not validate!" 77 | ndev.close 78 | exit 1 79 | end 80 | 81 | puts "OK!" 82 | 83 | print "Installing image ... please wait ... " 84 | rc = ndev.re.software_install!( :package => file_on_junos, :no_validate => true ) 85 | if rc != true 86 | puts rc 87 | end 88 | 89 | puts "OK!" 90 | 91 | ### use pry if you want to 'look around' 92 | ## -> binding.pry 93 | 94 | if ARGV[1] == 'reboot' 95 | puts "Rebooting!" 96 | ndev.re.reboot! 97 | end 98 | 99 | ndev.close 100 | -------------------------------------------------------------------------------- /examples/re_utils.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'pp' 3 | require 'net/scp' 4 | require 'net/netconf/jnpr' 5 | require 'junos-ez/stdlib' 6 | 7 | # login information for NETCONF session 8 | unless ARGV[0] 9 | puts "You must specify a target" 10 | exit 1 11 | end 12 | 13 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 14 | 15 | ## create a NETCONF object to manage the device and open the connection ... 16 | 17 | ndev = Netconf::SSH.new( login ) 18 | print "Connecting to device #{login[:target]} ... " 19 | ndev.open 20 | puts "OK!" 21 | 22 | binding.pry 23 | 24 | ## attach our private & utils that we need ... 25 | 26 | Junos::Ez::Provider( ndev ) 27 | Junos::Ez::RE::Utils( ndev, :re ) 28 | Junos::Ez::FS::Utils( ndev, :fs ) 29 | Junos::Ez::Config::Utils( ndev, :cu ) 30 | 31 | binding.pry 32 | 33 | ndev.close 34 | -------------------------------------------------------------------------------- /examples/simple.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'yaml' 3 | require 'net/netconf/jnpr' 4 | require 'junos-ez/stdlib' 5 | 6 | unless ARGV[0] 7 | puts "You must specify a target" 8 | exit 1 9 | end 10 | 11 | # login information for NETCONF session 12 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 13 | 14 | ## create a NETCONF object to manage the device and open the connection ... 15 | 16 | ndev = Netconf::SSH.new( login ) 17 | $stdout.print "Connecting to device #{login[:target]} ... " 18 | ndev.open 19 | $stdout.puts "OK!" 20 | 21 | ## Now bind providers to the device object. 22 | ## the 'Junos::Ez::Provider' must be first before all others 23 | ## this provider will setup the device 'facts'. The other providers 24 | ## allow you to define the instance variables; so this example 25 | ## is using 'l1_ports' and 'ip_ports', but you could name them 26 | ## what you like, yo! 27 | 28 | Junos::Ez::Provider( ndev ) 29 | Junos::Ez::L1ports::Provider( ndev, :l1_ports ) 30 | Junos::Ez::IPports::Provider( ndev, :ip_ports ) 31 | 32 | ## drop into interactive mode to play around ... let's look 33 | ## at what the device has for facts ... 34 | 35 | #-> ndev.facts.list 36 | #-> ndev.facts.catalog 37 | #-> ndev.fact :version 38 | 39 | ## now look at specific providers like the physical (l1) ports ... 40 | 41 | #-> ndev.l1_ports.list 42 | #-> ndev.l1_ports.catalog 43 | 44 | binding.pry 45 | 46 | ndev.close 47 | -------------------------------------------------------------------------------- /examples/st_hosts.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'pp' 3 | require 'yaml' 4 | require 'net/netconf/jnpr' 5 | require 'junos-ez/stdlib' 6 | 7 | # login information for NETCONF session 8 | 9 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 10 | 11 | ## create a NETCONF object to manage the device and open the connection ... 12 | 13 | ndev = Netconf::SSH.new( login ) 14 | $stdout.print "Connecting to device #{login[:target]} ... " 15 | ndev.open 16 | $stdout.puts "OK!" 17 | 18 | ## Now bind providers to the device object. 19 | ## the 'Junos::Ez::Provider' must be first before all others 20 | ## this provider will setup the device 'facts'. The other providers 21 | ## allow you to define the instance variables; so this example 22 | ## is using 'l1_ports' and 'ip_ports', but you could name them 23 | ## what you like, yo! 24 | 25 | Junos::Ez::Provider( ndev ) 26 | Junos::Ez::StaticHosts::Provider( ndev, :hosts ) 27 | 28 | pp ndev.hosts.list 29 | pp ndev.hosts.catalog 30 | 31 | binding.pry 32 | 33 | ndev.close 34 | -------------------------------------------------------------------------------- /examples/user.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'yaml' 3 | require 'net/netconf/jnpr' 4 | require 'junos-ez/stdlib' 5 | require 'junos-ez/srx' 6 | 7 | unless ARGV[0] 8 | puts "You must specify a target" 9 | exit 1 10 | end 11 | 12 | # login information for NETCONF session 13 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 14 | 15 | ## create a NETCONF object to manage the device and open the connection ... 16 | 17 | ndev = Netconf::SSH.new( login ) 18 | $stdout.print "Connecting to device #{login[:target]} ... " 19 | ndev.open 20 | $stdout.puts "OK!" 21 | 22 | Junos::Ez::Provider( ndev ) 23 | Junos::Ez::Users::Provider( ndev, :users ) 24 | Junos::Ez::UserAuths::Provider( ndev, :sshkeys ) 25 | Junos::Ez::Config::Utils( ndev, :cu ) 26 | 27 | user = ndev.users["jeremy"] 28 | user.load_ssh_key! :filename=>'/home/jschulman/.ssh/keys/key1.pub' 29 | 30 | binding.pry 31 | 32 | ndev.close 33 | -------------------------------------------------------------------------------- /examples/vlans.rb: -------------------------------------------------------------------------------- 1 | require 'net/netconf/jnpr' 2 | require 'junos-ez/stdlib' 3 | 4 | unless ARGV[0] 5 | puts "You must specify a target" 6 | exit 1 7 | end 8 | 9 | # login information for NETCONF session 10 | login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', } 11 | 12 | ## create a NETCONF object to manage the device and open the connection ... 13 | 14 | ndev = Netconf::SSH.new( login ) 15 | $stdout.print "Connecting to device #{login[:target]} ... " 16 | ndev.open 17 | $stdout.puts "OK!" 18 | 19 | Junos::Ez::Provider( ndev ) 20 | Junos::Ez::Config::Utils( ndev, :cu ) 21 | Junos::Ez::Vlans::Provider( ndev, :vlans ) 22 | #Junos::Ez::L1ports::Provider( ndev, :l1_ports ) 23 | Junos::Ez::L2ports::Provider( ndev, :l2_ports ) 24 | #Junos::Ez::IPports::Provider( ndev, :ip_ports ) 25 | 26 | #pp ndev.vlans.list 27 | #pp ndev.vlans.catalog 28 | 29 | binding.pry 30 | 31 | ndev.close 32 | -------------------------------------------------------------------------------- /junos-ez-stdlib.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # coding: utf-8 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'junos-ez/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'junos-ez-stdlib' 9 | spec.version = Junos::Ez::VERSION 10 | spec.authors = ['Jeremy Schulman', 'John Deatherage', 'Nitin Kumar', 'Priyal Jain', 'Ganesh Nalawade'] 11 | spec.email = 'jnpr-community-netdev@juniper.net' 12 | 13 | spec.summary = 'Junos EZ Framework - Standard Libraries' 14 | spec.description = 'Automation Framework for Junos/NETCONF: Facts, Providers, and Utils' 15 | spec.homepage = 'https://github.com/Juniper/ruby-junos-ez-stdlib' 16 | spec.license = 'BSD-2-Clause' 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 19 | 20 | spec.add_dependency('netconf', '~> 0.3.1') 21 | 22 | spec.add_development_dependency 'bundler', '~> 1.12' 23 | spec.add_development_dependency 'rake', '~> 10.0' 24 | spec.add_development_dependency 'rspec', '~> 3.0' 25 | spec.add_development_dependency 'rubocop', '~> 0.49.0' 26 | end 27 | -------------------------------------------------------------------------------- /lib/junos-ez/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Junos::Ez 2 | class NoProviderError < StandardError; end 3 | end 4 | -------------------------------------------------------------------------------- /lib/junos-ez/facts.rb: -------------------------------------------------------------------------------- 1 | require 'junos-ez/provider' 2 | 3 | ### ----------------------------------------------------------------- 4 | ### Junos::Ez module devices the toplevel Provider and associated 5 | ### Facts class & methods 6 | ### ----------------------------------------------------------------- 7 | 8 | module Junos::Ez 9 | 10 | attr_accessor :providers, :facts 11 | 12 | def self.Provider( ndev ) 13 | ndev.extend Junos::Ez 14 | ndev.providers = [] 15 | ndev.facts = Junos::Ez::Facts::Keeper.new( ndev ) 16 | ndev.facts.read! 17 | true 18 | end 19 | 20 | def fact( name ); facts[name] end 21 | end; 22 | 23 | module Junos::Ez::Facts 24 | 25 | class Keeper 26 | attr_accessor :known 27 | 28 | def initialize( ndev ) 29 | @ndev = ndev 30 | @known = Hash.new 31 | end 32 | 33 | def clear; @known.clear end 34 | 35 | def list; @known.keys end 36 | def list!; read!; list; end 37 | 38 | def catalog; @known end 39 | def catalog!; read!; catalog end 40 | 41 | def uses( *facts ) 42 | values = facts.collect do |f| 43 | self.send( "fact_read_#{f}", @ndev, @known ) unless @known[f] 44 | self[f] 45 | end 46 | (values.count == 1) ? values[0] : values 47 | end 48 | 49 | def self.define( fact, &block ) 50 | define_method( "fact_read_#{fact}".to_sym, block ) 51 | end 52 | 53 | def []=(key,value) 54 | @known[key] = value 55 | end 56 | 57 | def [](key) 58 | @known[key] 59 | end 60 | 61 | def read! 62 | @known.clear 63 | fact_readers = self.methods.grep /^fact_read_/ 64 | fact_readers.each do |getter| 65 | getter =~ /^fact_read_(\w+)/ 66 | fact = $1.to_sym 67 | self.send( getter, @ndev, @known ) unless @known[fact] 68 | end 69 | end 70 | 71 | end # class 72 | end 73 | 74 | ### ----------------------------------------------------------------- 75 | ### Load all of the fact files 76 | ### ----------------------------------------------------------------- 77 | 78 | require 'junos-ez/facts/chassis' 79 | require 'junos-ez/facts/personality' 80 | require 'junos-ez/facts/version' 81 | require 'junos-ez/facts/switch_style' 82 | require 'junos-ez/facts/ifd_style' 83 | 84 | -------------------------------------------------------------------------------- /lib/junos-ez/facts/chassis.rb: -------------------------------------------------------------------------------- 1 | Junos::Ez::Facts::Keeper.define( :chassis ) do |ndev, facts| 2 | 3 | inv_info = ndev.rpc.get_chassis_inventory 4 | errs = inv_info.xpath('//output')[0] 5 | 6 | if errs and errs.text.include? "This command can only be used on the master routing engine" 7 | raise Junos::Ez::NoProviderError, "Chef can only be used on master routing engine !!" 8 | end 9 | 10 | chassis = inv_info.xpath('chassis') 11 | 12 | facts[:hardwaremodel] = chassis.xpath('description').text 13 | facts[:serialnumber] = chassis.xpath('serial-number').text 14 | 15 | cfg = ndev.rpc.get_configuration{|xml| 16 | xml.system { 17 | xml.send(:'host-name') 18 | xml.send(:'domain-name') 19 | } 20 | } 21 | 22 | facts[:hostname] = cfg.xpath('//host-name').text 23 | facts[:domain] = cfg.xpath('//domain-name').text 24 | facts[:fqdn] = facts[:hostname] 25 | facts[:fqdn] += ".#{facts[:domain]}" unless facts[:domain].empty? 26 | 27 | end 28 | 29 | Junos::Ez::Facts::Keeper.define( :master ) do |ndev, facts| 30 | uses :routingengines 31 | end 32 | 33 | Junos::Ez::Facts::Keeper.define( :routingengines ) do |ndev, facts| 34 | 35 | re_facts = ['mastership-state','status','model','up-time','last-reboot-reason'] 36 | re_info = ndev.rpc.get_route_engine_information 37 | re_info.xpath('//route-engine').each do |re| 38 | slot_id = re.xpath('slot').text || "0" 39 | slot = ("RE" + slot_id).to_sym 40 | facts[slot] = Hash[ re_facts.collect{ |ele| [ ele.tr('-','_').to_sym, re.xpath(ele).text ] } ] 41 | if facts[slot][:mastership_state].empty? 42 | facts[slot].delete :mastership_state 43 | else 44 | facts[:master] = slot_id if facts[slot][:mastership_state] == 'master' 45 | end 46 | end 47 | 48 | end 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/junos-ez/facts/ifd_style.rb: -------------------------------------------------------------------------------- 1 | Junos::Ez::Facts::Keeper.define( :ifd_style ) do |ndev, facts| 2 | persona,sw_style = uses :personality,:switch_style 3 | 4 | facts[:ifd_style] = case persona 5 | when :SWITCH 6 | if sw_style == :VLAN_L2NG 7 | :CLASSIC 8 | else 9 | :SWITCH 10 | end 11 | else 12 | :CLASSIC 13 | end 14 | 15 | end 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/junos-ez/facts/personality.rb: -------------------------------------------------------------------------------- 1 | Junos::Ez::Facts::Keeper.define( :personality ) do |ndev, facts| 2 | 3 | uses :chassis, :routingengines 4 | model = facts[:hardwaremodel] 5 | 6 | examine = ( model != "Virtual Chassis" ) ? model : facts.select {|k,v| k.match(/^RE[0..9]+/) }.values[0][:model] 7 | 8 | facts[:personality] = case examine 9 | when /^(EX)|(QFX)|(PTX)|(OCX)/i 10 | :SWITCH 11 | when /^MX/i 12 | :MX 13 | when /^vMX/i 14 | facts[:virtual] = true 15 | :MX 16 | when /SRX(\d){3}/i 17 | :SRX_BRANCH 18 | when /junosv-firefly/i 19 | facts[:virtual] = true 20 | :SRX_BRANCH 21 | when /SRX(\d){4}/i 22 | :SRX_HIGHEND 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /lib/junos-ez/facts/switch_style.rb: -------------------------------------------------------------------------------- 1 | Junos::Ez::Facts::Keeper.define( :switch_style ) do |ndev, facts| 2 | f_persona = uses :personality 3 | 4 | model = facts[:hardwaremodel] 5 | examine = ( model != "Virtual Chassis" ) ? model : facts.select {|k,v| k.match(/^RE[0-9]+/) }.values[0][:model] 6 | 7 | facts[:switch_style] = case f_persona 8 | when :SWITCH, :SRX_BRANCH 9 | case examine 10 | when /junosv-firefly/i 11 | :NONE 12 | when /^(ex9)|(ex43)|(ocx)/i 13 | :VLAN_L2NG 14 | when /^(qfx)/i 15 | if facts[:version][0..3].to_f >= 13.2 16 | :VLAN_L2NG 17 | else 18 | :VLAN 19 | end 20 | else 21 | :VLAN 22 | end 23 | when :MX, :SRX_HIGHEND 24 | :BRIDGE_DOMAIN 25 | else 26 | :NONE 27 | end 28 | 29 | end 30 | 31 | 32 | -------------------------------------------------------------------------------- /lib/junos-ez/facts/version.rb: -------------------------------------------------------------------------------- 1 | Junos::Ez::Facts::Keeper.define( :version ) do |ndev, facts| 2 | 3 | f_master, f_persona = uses :master, :personality 4 | 5 | case f_persona 6 | when :MX 7 | begin 8 | swver = ndev.rpc.command "show version invoke-on all-routing-engines" 9 | rescue Netconf::RpcError 10 | swver = ndev.rpc.command "show version" 11 | end 12 | when :SWITCH 13 | ## most EX switches support the virtual-chassis feature, so the 'all-members' option would be valid 14 | ## in some products, this options is not valid (i.e. not vc-capable. so we're going to try for vc, and if that 15 | ## throws an exception we'll rever to non-VC 16 | 17 | begin 18 | swver = ndev.rpc.command "show version all-members" 19 | rescue Netconf::RpcError 20 | facts[:vc_capable] = false 21 | swver = ndev.rpc.command "show version" 22 | else 23 | facts[:vc_capable] = true 24 | end 25 | else 26 | swver = ndev.rpc.command "show version" 27 | end 28 | 29 | if swver.name == 'multi-routing-engine-results' 30 | swver_infos = swver.xpath('//software-information') 31 | swver_infos.each do |re_sw| 32 | re_name = re_sw.xpath('preceding-sibling::re-name').text.upcase 33 | ver_key = ('version_' + re_name).to_sym 34 | 35 | if re_sw.at_xpath('//junos-version') 36 | facts[ver_key] = re_sw.xpath('//junos-version').text 37 | else 38 | re_sw.xpath('package-information[1]/comment').text =~ /\[(.*)\]/ 39 | facts[ver_key] = $1 40 | end 41 | end 42 | master_id = f_master 43 | unless master_id.nil? 44 | facts[:version] = 45 | facts[("version_" + "RE" + master_id).to_sym] || 46 | facts[("version_" + "LOCALRE").to_sym] || 47 | facts[('version_' + "FPC" + master_id).to_sym] 48 | end 49 | else 50 | if swver.at_xpath('//junos-version') 51 | facts[:version] = swver.xpath('//junos-version').text 52 | else 53 | junos = swver.xpath('//package-information[name = "junos"]/comment').text 54 | junos =~ /\[(.*)\]/ 55 | facts[:version] = $1 56 | end 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /lib/junos-ez/group.rb: -------------------------------------------------------------------------------- 1 | require "junos-ez/provider" 2 | 3 | module Junos::Ez::Group 4 | 5 | PROPERTIES = [ 6 | :format, # [:set, :text, :xml] 7 | :path, # Configuration file path 8 | ] 9 | 10 | def self.Provider( ndev, varsym ) 11 | newbie = Junos::Ez::Group::Provider::new( ndev ) 12 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 13 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 14 | end 15 | 16 | class Provider < Junos::Ez::Provider::Parent 17 | # common parenting goes here ... if we were to 18 | # subclass the objects ... not doing that now 19 | end 20 | 21 | end 22 | 23 | class Junos::Ez::Group::Provider 24 | 25 | ### --------------------------------------------------------------- 26 | ### XML top placement 27 | ### --------------------------------------------------------------- 28 | 29 | def xml_at_top 30 | xml = Nokogiri::XML::Builder.new {|xml| xml.configuration { 31 | xml.groups { 32 | xml.name @name 33 | return xml 34 | } 35 | }} 36 | end 37 | 38 | ### --------------------------------------------------------------- 39 | ### XML property readers 40 | ### --------------------------------------------------------------- 41 | 42 | def xml_get_has_xml( xml ) 43 | xml.xpath('//groups')[0] 44 | end 45 | 46 | def xml_read_parser( as_xml, as_hash ) 47 | set_has_status( as_xml, as_hash ) 48 | 49 | grp = as_xml.xpath('name').text 50 | as_hash[:name] = grp unless grp.empty? 51 | 52 | end 53 | 54 | 55 | ### --------------------------------------------------------------- 56 | ### XML property writers 57 | ### --------------------------------------------------------------- 58 | 59 | def xml_change_path( xml ) 60 | end 61 | 62 | def xml_change_format( xml ) 63 | end 64 | 65 | ### --------------------------------------------------------------- 66 | ### XML on-create 67 | ### --------------------------------------------------------------- 68 | 69 | def xml_on_create( xml ) 70 | end 71 | 72 | ### --------------------------------------------------------------- 73 | ### XML on-delete 74 | ### --------------------------------------------------------------- 75 | def xml_on_delete( xml ) 76 | end 77 | 78 | def write_xml_config!( xml, opts = {} ) 79 | if (@should[:_exist] == true) 80 | _load ( xml ) 81 | @should[:format] = 'xml' unless @should[:format] 82 | begin 83 | attr = {} 84 | attr[:action] = 'replace' 85 | attr[:format] = @should[:format].to_s 86 | result = @ndev.rpc.load_configuration( @config.to_s, attr ) 87 | rescue Netconf::RpcError => e 88 | errs = e.rsp.xpath('//rpc-error[error-severity = "error"]') 89 | raise e unless errs.empty? 90 | e.rsp 91 | else 92 | result 93 | end 94 | else 95 | super(xml) 96 | end 97 | _apply_group 98 | end 99 | 100 | def write! 101 | return nil if @should.empty? 102 | 103 | @should[:_exist] ||= true 104 | @should[:_active] ||= :true 105 | # load the conifguration from file and apply under group 106 | # hirerachy 107 | rsp = write_xml_config!( xml_at_top.doc.root ) 108 | 109 | # copy the 'should' values into the 'has' values now that 110 | # they've been written back to Junos 111 | 112 | @has.merge! @should 113 | @should.clear 114 | 115 | return true 116 | end 117 | 118 | end 119 | 120 | 121 | ##### --------------------------------------------------------------- 122 | ##### Provider collection methods 123 | ##### --------------------------------------------------------------- 124 | 125 | class Junos::Ez::Group::Provider 126 | 127 | def build_list 128 | grp_cfgs = @ndev.rpc.get_configuration{|xml| 129 | xml.send(:'groups') 130 | }.xpath('groups/name').collect do |item| 131 | item.text 132 | end 133 | return grp_cfgs 134 | end 135 | 136 | def build_catalog 137 | return @catalog if list!.empty? 138 | list.each do |grp_name| 139 | @ndev.rpc.get_configuration{ |xml| 140 | xml.groups { 141 | xml.name grp_name 142 | } 143 | }.xpath('groups').each do |as_xml| 144 | @catalog[grp_name] = {} 145 | xml_read_parser( as_xml, @catalog[grp_name] ) 146 | end 147 | end 148 | @catalog 149 | end 150 | end 151 | 152 | ##### --------------------------------------------------------------- 153 | ##### _PRIVATE methods 154 | ##### --------------------------------------------------------------- 155 | 156 | class Junos::Ez::Group::Provider 157 | 158 | def _load ( xml ) 159 | return @config = nil if ( @should[:_exist] == false ) 160 | admin = '' 161 | if @should[:format].to_s == 'set' 162 | @config = "\ndelete groups #{@name}\n" + 163 | "edit groups #{@name}\n" + 164 | File.read( @should[:path] ) 165 | admin = @should[:_active] == :false ? 'deactivate' : 'activate' 166 | @config += "\nquit\n" 167 | @config += "\n#{admin} groups #{@name}" 168 | 169 | elsif @should[:format].to_s == 'text' 170 | admin = @should[:_active] == :false ? 'inactive' : 'active' 171 | admin += ": " unless admin.empty? 172 | @config = "groups {\n#{admin} replace: #{@name} {\n" + 173 | File.read( @should[:path] ) + "\n}\n}" 174 | 175 | elsif @should[:format].to_s == 'xml' 176 | xml.at_xpath('groups') << File.read( @should[:path]) 177 | @config = xml 178 | end 179 | return @config 180 | end 181 | 182 | def _apply_group 183 | cfg = Netconf::JunosConfig.new(:TOP) 184 | xml = cfg.doc 185 | Nokogiri::XML::Builder.with( xml.at_xpath( 'configuration' )) do |dot| 186 | if @config and @should[:_active] == :true 187 | dot.send :'apply-groups', @name 188 | else 189 | dot.send :'apply-groups', @name, Netconf::JunosConfig::DELETE 190 | end 191 | end 192 | begin 193 | attr = {} 194 | attr[:action] = 'replace' 195 | attr[:format] = 'xml' 196 | result = @ndev.rpc.load_configuration( xml, attr ) 197 | rescue Netconf::RpcError => e 198 | errs = e.rsp.xpath('//rpc-error[error-severity = "error"]') 199 | raise e unless errs.empty? 200 | e.rsp 201 | else 202 | result 203 | end 204 | end 205 | end 206 | 207 | -------------------------------------------------------------------------------- /lib/junos-ez/ip_ports.rb: -------------------------------------------------------------------------------- 1 | 2 | require "junos-ez/provider" 3 | 4 | module Junos::Ez::IPports 5 | 6 | PROPERTIES = [ 7 | :admin, # [:up, :down] 8 | :description, # general description text 9 | :tag_id, # VLAN tag-id for vlan-tag enabled ports 10 | :mtu, # MTU value as number 11 | :address, # ip/prefix as text, e.g. "192.168.10.22/24" 12 | :acl_in, # input ACL name 13 | :acl_out, # output ACL name 14 | ] 15 | 16 | def self.Provider( ndev, varsym ) 17 | newbie = Junos::Ez::IPports::Provider::CLASSIC.new( ndev ) 18 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 19 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 20 | end 21 | 22 | class Provider < Junos::Ez::Provider::Parent 23 | # common parenting goes here ... 24 | end 25 | 26 | end 27 | 28 | require 'junos-ez/ip_ports/classic' 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/junos-ez/ip_ports/classic.rb: -------------------------------------------------------------------------------- 1 | class Junos::Ez::IPports::Provider::CLASSIC < Junos::Ez::IPports::Provider 2 | 3 | ### --------------------------------------------------------------- 4 | ### XML top placement 5 | ### --------------------------------------------------------------- 6 | 7 | def xml_at_top 8 | 9 | # if just the IFD is given as the name, default to unit "0" 10 | @ifd, @ifd_unit = @name.split '.' 11 | @ifd_unit ||= "0" 12 | 13 | Nokogiri::XML::Builder.new{ |x| x.configuration{ 14 | x.interfaces { x.interface { x.name @ifd 15 | x.unit { 16 | x.name @ifd_unit 17 | return x 18 | } 19 | }} 20 | }} 21 | end 22 | 23 | def xml_element_rename( new_name ) 24 | 25 | # if just the IFD is given as the name, default to unit "0" 26 | n_ifd, n_ifl = new_name.split '.' 27 | n_ifl ||= "0" 28 | 29 | # do not allow rename to different IFD. 30 | return false unless @ifd == n_ifd 31 | 32 | # return the new element name 33 | return n_ifl 34 | end 35 | 36 | ### --------------------------------------------------------------- 37 | ### XML readers 38 | ### --------------------------------------------------------------- 39 | 40 | def xml_get_has_xml( xml ) 41 | xml.xpath('//unit')[0] 42 | end 43 | 44 | def xml_read_parser( as_xml, as_hash ) 45 | set_has_status( as_xml, as_hash ) 46 | 47 | as_hash[:admin] = as_xml.xpath('disable').empty? ? :up : :down 48 | ifa_inet = as_xml.xpath('family/inet') 49 | 50 | xml_when_item(as_xml.xpath('vlan-id')){ |i| as_hash[:tag_id] = i.text.to_i } 51 | xml_when_item(as_xml.xpath('description')){ |i| as_hash[:description] = i.text } 52 | xml_when_item(ifa_inet.xpath('mtu')){ |i| as_hash[:mtu] = i.text.to_i } 53 | 54 | # @@@ assuming a single IP address; prolly need to be more specific ... 55 | as_hash[:address] = ifa_inet.xpath('address/name').text || nil 56 | 57 | # check for firewall-filters (aka ACLs) 58 | if (fw_acl = ifa_inet.xpath('filter')[0]) 59 | xml_when_item( fw_acl.xpath('input/filter-name')){ |i| as_hash[:acl_in] = i.text.strip } 60 | xml_when_item( fw_acl.xpath('output/filter-name')){ |i| as_hash[:acl_out] = i.text.strip } 61 | end 62 | 63 | return true 64 | end 65 | 66 | ### --------------------------------------------------------------- 67 | ### XML writers 68 | ### --------------------------------------------------------------- 69 | 70 | def xml_change_address( xml ) 71 | xml.family { xml.inet { 72 | # delete the old address and replace it with the new one ... 73 | if @has[:address] 74 | xml.address( Netconf::JunosConfig::DELETE ) { xml.name @has[:address] } 75 | end 76 | xml.address { xml.name @should[:address] } 77 | }} 78 | end 79 | 80 | def xml_change_tag_id( xml ) 81 | xml_set_or_delete( xml, 'vlan-id', @should[:tag_id] ) 82 | end 83 | 84 | def xml_change_mtu( xml ) 85 | xml.family { xml.inet { 86 | xml_set_or_delete( xml, 'mtu', @should[:mtu] ) 87 | }} 88 | end 89 | 90 | def xml_change_acl_in( xml ) 91 | xml.family { xml.inet { xml.filter { xml.input { 92 | xml_set_or_delete( xml, 'filter-name', @should[:acl_in] ) 93 | }}}} 94 | end 95 | 96 | def xml_change_acl_out( xml ) 97 | xml.family { xml.inet { xml.filter { xml.output { 98 | xml_set_or_delete( xml, 'filter-name', @should[:acl_out] ) 99 | }}}} 100 | end 101 | 102 | end 103 | 104 | ##### --------------------------------------------------------------- 105 | ##### Resource Methods 106 | ##### --------------------------------------------------------------- 107 | 108 | class Junos::Ez::IPports::Provider::CLASSIC 109 | def status 110 | got = @ndev.rpc.get_interface_information( :interface_name => @ifd+'.'+@ifd_unit ) 111 | ifs = got.xpath('logical-interface')[0] 112 | ret_h = {} 113 | ret_h[:l1_oper_status] = (ifs.xpath('if-config-flags/iff-device-down')[0]) ? :down : :up 114 | ret_h[:oper_status] = (ifs.xpath('address-family//ifaf-down')[0]) ? :down : :up 115 | ret_h[:snmp_index] = ifs.xpath('snmp-index').text.to_i 116 | ret_h[:packets_rx] = ifs.xpath('traffic-statistics/input-packets').text.to_i 117 | ret_h[:packets_tx] = ifs.xpath('traffic-statistics/output-packets').text.to_i 118 | ret_h 119 | end 120 | end 121 | 122 | ##### --------------------------------------------------------------- 123 | ##### Provider collection methods 124 | ##### --------------------------------------------------------------- 125 | 126 | class Junos::Ez::IPports::Provider::CLASSIC 127 | 128 | def build_list 129 | from_junos_get_ifa_xml.collect do |ifa| 130 | ifa.xpath('name').text.strip 131 | end 132 | end 133 | 134 | def build_catalog 135 | @catalog = {} 136 | 137 | ## do the equivalent of "show interfaces ..." to retrieve the list 138 | ## of known interfaces that have an IFA == 'inet'. Note that this 139 | ## list will *not* include anything that has been deactivated. 140 | 141 | ifa_list = from_junos_get_ifa_xml 142 | 143 | ## from this list of IFA, retrieve the configurations 144 | 145 | got_xml_cfg = @ndev.rpc.get_configuration do |cfg| 146 | cfg.interfaces { 147 | ifa_list.each do |ifa| 148 | ifa_name = ifa.xpath('name').text.strip 149 | ifa_ifd, ifa_ifl = ifa_name.split '.' 150 | cfg.interface { 151 | cfg.name ifa_ifd 152 | cfg.unit { cfg.name ifa_ifl } 153 | } 154 | end 155 | } 156 | end 157 | 158 | ## now create the object property hashes for each of the instances 159 | 160 | got_xml_cfg.xpath('interfaces/interface/unit').each do |ifl| 161 | ifd = ifl.xpath('preceding-sibling::name').text.strip 162 | unit = ifl.xpath('name').text.strip 163 | obj_name = ifd + '.' + unit 164 | 165 | @catalog[obj_name] = {} 166 | xml_read_parser( ifl, @catalog[obj_name] ) 167 | end 168 | 169 | return @catalog 170 | end 171 | 172 | private 173 | 174 | def from_junos_get_ifa_xml 175 | 176 | xml_data = @ndev.rpc.get_interface_information( 177 | :terse => true, 178 | ) 179 | 180 | ifa_list = xml_data.xpath('interface-information/logical-interface[normalize-space(address-family/address-family-name) = "inet"]') 181 | 182 | end 183 | 184 | end 185 | -------------------------------------------------------------------------------- /lib/junos-ez/l1_ports.rb: -------------------------------------------------------------------------------- 1 | require "junos-ez/provider" 2 | 3 | module Junos::Ez::L1ports 4 | 5 | PROPERTIES = [ 6 | :admin, # [ :up, :down ] 7 | :description, # string 8 | :mtu, # number 9 | :speed, # [ :auto, '10m', '100m', '1g', '10g' ] 10 | :duplex, # [ :auto, :half, :full ] 11 | :unit_count, # number of configured units 12 | ] 13 | 14 | IFS_NAME_FILTER = '[fgx]e-*' 15 | 16 | def self.Provider( ndev, varsym ) 17 | newbie = case ndev.fact( :ifd_style ) 18 | when :SWITCH 19 | Junos::Ez::L1ports::Provider::SWITCH.new( ndev ) 20 | when :CLASSIC 21 | Junos::Ez::L1ports::Provider::CLASSIC.new( ndev ) 22 | end 23 | 24 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 25 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 26 | end 27 | 28 | end 29 | 30 | class Junos::Ez::L1ports::Provider < Junos::Ez::Provider::Parent 31 | 32 | ### --------------------------------------------------------------- 33 | ### XML readers 34 | ### --------------------------------------------------------------- 35 | 36 | def xml_get_has_xml( xml ) 37 | xml.xpath('//interface')[0] 38 | end 39 | 40 | def xml_change_mtu( xml ) 41 | xml_set_or_delete( xml, 'mtu', @should[:mtu] ) 42 | end 43 | 44 | ### --------------------------------------------------------------- 45 | ### Collection methods 46 | ### --------------------------------------------------------------- 47 | 48 | def build_list 49 | @ndev.rpc.get_interface_information({ 50 | :media => true, 51 | :terse => true, 52 | :interface_name => Junos::Ez::L1ports::IFS_NAME_FILTER 53 | }).xpath('physical-interface/name').collect do |ifs| 54 | ifs.text.strip 55 | end 56 | end 57 | 58 | def build_catalog 59 | @catalog = {} 60 | 61 | # we could have a large list of interfaces, so 62 | # we need to break this up into individual "gets" 63 | 64 | list!.each do |ifs_name| 65 | @ndev.rpc.get_configuration{ |xml| 66 | xml.interfaces { 67 | xml.interface { 68 | xml.name ifs_name 69 | xml_read_filter( xml ) 70 | } 71 | } 72 | }.xpath('interfaces/interface').each do |ifs_xml| 73 | @catalog[ifs_name] = {} 74 | xml_read_parser( ifs_xml, @catalog[ifs_name] ) 75 | end 76 | end 77 | 78 | return @catalog 79 | end 80 | 81 | ### --------------------------------------------------------------- 82 | ### Resource methods 83 | ### --------------------------------------------------------------- 84 | 85 | ## returns a Hash of status information, from "show interface ..." 86 | ## basic information, not absolutely everything. but if a 87 | ## block is given, then pass the XML to the block. 88 | 89 | def status 90 | 91 | got = @ndev.rpc.get_interface_information(:interface_name => @name, :media => true ) 92 | phy = got.xpath('physical-interface')[0] 93 | return nil unless phy 94 | 95 | ret_h = {} 96 | ret_h[:macaddr] = phy.xpath('current-physical-address').text.strip 97 | xml_when_item(phy.xpath('description')){|i| ret_h[:description] = i.text.strip } 98 | ret_h[:oper_status] = phy.xpath('oper-status').text.strip 99 | ret_h[:admin_status] = phy.xpath('admin-status').text.strip 100 | ret_h[:mtu] = phy.xpath('mtu').text.to_i 101 | ret_h[:speed] = {:admin => phy.xpath('speed').text.strip } 102 | ret_h[:duplex] = {:admin => phy.xpath('duplex').text.strip } 103 | ret_h[:autoneg] = phy.xpath('if-auto-negotiation').text.strip 104 | 105 | if ret_h[:autoneg] == "enabled" 106 | autoneg = phy.xpath('ethernet-autonegotiation')[0] 107 | ret_h[:speed][:oper] = autoneg.xpath('link-partner-speed').text.strip 108 | ret_h[:duplex][:oper] = autoneg.xpath('link-partner-duplexity').text.strip 109 | end 110 | 111 | # if a block is given, then it means the caller wants to process the XML data. 112 | yield( phy ) if block_given? 113 | 114 | ret_h 115 | end 116 | 117 | end 118 | 119 | require 'junos-ez/l1_ports/switch' 120 | require 'junos-ez/l1_ports/classic' 121 | 122 | -------------------------------------------------------------------------------- /lib/junos-ez/l1_ports/classic.rb: -------------------------------------------------------------------------------- 1 | class Junos::Ez::L1ports::Provider::CLASSIC < Junos::Ez::L1ports::Provider 2 | 3 | ### --------------------------------------------------------------- 4 | ### XML top placement 5 | ### --------------------------------------------------------------- 6 | 7 | def xml_at_top 8 | xml = Nokogiri::XML::Builder.new {|xml| xml.configuration { 9 | xml.interfaces { 10 | xml.interface { 11 | xml.name @name 12 | return xml 13 | } 14 | } 15 | }} 16 | end 17 | 18 | ### --------------------------------------------------------------- 19 | ### XML property readers 20 | ### --------------------------------------------------------------- 21 | 22 | def xml_read_filter( xml ) 23 | xml.description 24 | xml.disable 25 | xml.mtu 26 | xml.speed 27 | xml.send(:'link-mode') 28 | xml.unit({:recurse => 'false'}) 29 | end 30 | 31 | def xml_config_read! 32 | xml = xml_at_top 33 | xml_read_filter( xml ) 34 | @ndev.rpc.get_configuration( xml ) 35 | end 36 | 37 | def xml_read_parser( as_xml, as_hash ) 38 | set_has_status( as_xml, as_hash ) 39 | 40 | as_hash[:admin] = as_xml.xpath('disable').empty? ? :up : :down 41 | 42 | unless (desc = as_xml.xpath('description').text.chomp).empty? 43 | as_hash[:description] = desc 44 | end 45 | 46 | if mtu = as_xml.xpath('mtu')[0]; as_hash[:mtu] = mtu.text.to_i end 47 | 48 | as_hash[:duplex] = case as_xml.xpath('link-mode').text.chomp 49 | when 'full-duplex' then :full 50 | when 'half-duplex' then :half 51 | else :auto 52 | end 53 | 54 | as_hash[:speed] = ( speed = as_xml.xpath('speed')[0] ) ? speed.text : :auto 55 | as_hash[:unit_count] = as_xml.xpath('unit').count 56 | 57 | return true 58 | end 59 | 60 | ### --------------------------------------------------------------- 61 | ### XML property writers 62 | ### --------------------------------------------------------------- 63 | 64 | def xml_change_speed( xml ) 65 | if @should[:speed] == :auto 66 | if is_new? 67 | xml.speed Netconf::JunosConfig::DELETE 68 | end 69 | else 70 | xml.speed @should[:speed] 71 | end 72 | end 73 | 74 | def xml_change_duplex( xml ) 75 | if @should[:duplex] == :auto 76 | unless is_new? 77 | xml.send( :'link-mode', Netconf::JunosConfig::DELETE ) 78 | end 79 | else 80 | xml.send( :'link-mode', case @should[:duplex] 81 | when :full then 'full-duplex' 82 | when :half then 'half-duplex' 83 | end ) 84 | end 85 | end 86 | 87 | end 88 | -------------------------------------------------------------------------------- /lib/junos-ez/l1_ports/switch.rb: -------------------------------------------------------------------------------- 1 | class Junos::Ez::L1ports::Provider::SWITCH < Junos::Ez::L1ports::Provider 2 | 3 | ### --------------------------------------------------------------- 4 | ### XML top placement 5 | ### --------------------------------------------------------------- 6 | 7 | def xml_at_top 8 | xml = Nokogiri::XML::Builder.new {|xml| xml.configuration { 9 | xml.interfaces { 10 | xml.interface { 11 | xml.name @name 12 | return xml 13 | } 14 | } 15 | }} 16 | end 17 | 18 | ### --------------------------------------------------------------- 19 | ### XML property readers 20 | ### --------------------------------------------------------------- 21 | 22 | def xml_read_filter( xml ) 23 | xml.description 24 | xml.disable 25 | xml.mtu 26 | xml.send(:'ether-options') 27 | xml.unit({:recurse => 'false'}) 28 | end 29 | 30 | def xml_config_read! 31 | xml = xml_at_top 32 | xml_read_filter( xml ) 33 | @ndev.rpc.get_configuration( xml ) 34 | end 35 | 36 | def xml_read_parser( as_xml, as_hash ) 37 | set_has_status( as_xml, as_hash ) 38 | 39 | xml_when_item(as_xml.xpath('description')){|i| as_hash[:description] = i.text} 40 | as_hash[:admin] = as_xml.xpath('disable').empty? ? :up : :down 41 | xml_when_item(as_xml.xpath('mtu')){|i| as_hash[:mtu] = i.text.to_i } 42 | 43 | phy_options = as_xml.xpath('ether-options') 44 | if phy_options.empty? 45 | as_hash[:speed] = :auto 46 | as_hash[:duplex] = :auto 47 | else 48 | ## :duplex 49 | as_hash[:duplex] = case phy_options.xpath('link-mode').text.chomp 50 | when 'full-duplex' then :full 51 | when 'half-duplex' then :half 52 | else :auto 53 | end 54 | ## :speed 55 | if speed = phy_options.xpath('speed')[0] 56 | as_hash[:speed] = _speed_from_junos_( speed.first_element_child.name ) 57 | else 58 | as_hash[:speed] = :auto 59 | end 60 | end 61 | 62 | as_hash[:unit_count] = as_xml.xpath('unit').count 63 | return true 64 | end 65 | 66 | ### --------------------------------------------------------------- 67 | ### XML property writers 68 | ### --------------------------------------------------------------- 69 | 70 | def xml_change_speed( xml ) 71 | xml.send(:'ether-options') { 72 | xml.speed { 73 | if @should[:speed] == :auto 74 | unless @has[:speed] == :auto 75 | xml.send( _speed_to_junos_( @has[:speed] ), Netconf::JunosConfig::DELETE ) 76 | end 77 | else 78 | xml.send( _speed_to_junos_( @should[:speed] )) 79 | end 80 | } 81 | } 82 | end 83 | 84 | def xml_change_duplex( xml ) 85 | xml.send(:'ether-options') { 86 | if @should[:duplex] == :auto 87 | unless @has[:duplex] == :auto 88 | xml.send( :'link-mode', Netconf::JunosConfig::DELETE ) 89 | end 90 | else 91 | xml.send( :'link-mode', case @should[:duplex] 92 | when :full then 'full-duplex' 93 | when :half then 'half-duplex' 94 | end ) 95 | end 96 | } 97 | end 98 | 99 | 100 | end 101 | 102 | ### ----------------------------------------------------------------- 103 | ### PRIVATE METHODS 104 | ### ----------------------------------------------------------------- 105 | 106 | class Junos::Ez::L1ports::Provider::SWITCH 107 | private 108 | 109 | def _speed_to_junos_( pval ) 110 | # @@@ TODO: could remove case-statement and to 111 | # @@@ string processing ... 112 | case pval 113 | when '10g' then :'ethernet-10g' 114 | when '1g' then :'ethernet-1g' 115 | when '100m' then :'ethernet-100m' 116 | when '10m' then :'ethernet-10m' 117 | else :auto 118 | end 119 | end 120 | 121 | def _speed_from_junos_( jval ) 122 | # @@@ TODO: could remove case-statement and to 123 | # @@@ string processing ... 124 | case jval 125 | when 'ethernet-100m' then '100m' 126 | when 'ethernet-10m' then '10m' 127 | when 'ethernet-1g' then '1g' 128 | when 'ethernet-10g' then '10g' 129 | else :auto 130 | end 131 | end 132 | 133 | end 134 | 135 | -------------------------------------------------------------------------------- /lib/junos-ez/l2_ports.rb: -------------------------------------------------------------------------------- 1 | 2 | require "junos-ez/provider" 3 | 4 | module Junos::Ez::L2ports 5 | 6 | PROPERTIES = [ 7 | :description, # String | nil 8 | :untagged_vlan, # String | nil 9 | :tagged_vlans, # Set of String | nil 10 | :vlan_tagging # true | false 11 | ] 12 | 13 | def self.Provider( ndev, varsym ) 14 | 15 | newbie = case ndev.fact( :switch_style ) 16 | when :VLAN 17 | Junos::Ez::L2ports::Provider::VLAN.new( ndev ) 18 | when :VLAN_L2NG 19 | Junos::Ez::L2ports::Provider::VLAN_L2NG.new( ndev ) 20 | when :BRIDGE_DOMAIN 21 | Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN.new(ndev) 22 | #raise ArgumentError, "under development" 23 | # Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN.new( ndev ) 24 | end 25 | 26 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 27 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 28 | end 29 | 30 | class Provider < Junos::Ez::Provider::Parent 31 | # common parenting ... 32 | 33 | def is_trunk? 34 | @has[:vlan_tagging] == true 35 | end 36 | 37 | def should_trunk? 38 | (@should[:vlan_tagging].nil?) ? @has[:vlan_tagging] : @should[:vlan_tagging] 39 | end 40 | 41 | def mode_changed? 42 | return true if is_new? 43 | return false if @should[:vlan_tagging].nil? 44 | @should[:vlan_tagging] != @has[:vlan_tagging] 45 | end 46 | 47 | ### --------------------------------------------------------------- 48 | ### XML overload 'activate/deactivate' since we need to modify 49 | ### this at the 'unit' level and not at the 'family' level 50 | ### --------------------------------------------------------------- 51 | 52 | def xml_change__active( xml ) 53 | par = xml.instance_variable_get(:@parent).at_xpath('ancestor::interface') 54 | value = @should[:_active] ? 'active' : 'inactive' 55 | par[value] = value # attribute name is same as value 56 | end 57 | 58 | end 59 | 60 | end 61 | 62 | require 'junos-ez/l2_ports/vlan' 63 | require 'junos-ez/l2_ports/vlan_l2ng' 64 | require 'junos-ez/l2_ports/bridge_domain' 65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/junos-ez/lag_ports.rb: -------------------------------------------------------------------------------- 1 | require "junos-ez/provider" 2 | 3 | module Junos::Ez::LAGports 4 | 5 | PROPERTIES = [ 6 | :links, # Set of interface names 7 | :minimum_links, # nil or Number > 0 # optional 8 | :lacp, # [ :active, :passive, :disabled ] # optional 9 | ] 10 | 11 | def self.Provider( ndev, varsym ) 12 | newbie = Junos::Ez::LAGports::Provider::new( ndev ) 13 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 14 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 15 | end 16 | 17 | class Provider < Junos::Ez::Provider::Parent 18 | # common parenting goes here ... if we were to 19 | # subclass the objects ... not doing that now 20 | end 21 | 22 | end 23 | 24 | class Junos::Ez::LAGports::Provider 25 | 26 | ### --------------------------------------------------------------- 27 | ### XML top placement 28 | ### --------------------------------------------------------------- 29 | 30 | # LAG ports sit at the toplevel interface 31 | 32 | def xml_at_top 33 | Nokogiri::XML::Builder.new {|xml| xml.configuration { 34 | xml.interfaces { xml.interface { 35 | xml.name @name 36 | return xml 37 | }} 38 | }} 39 | end 40 | 41 | ###----------------------------------------------------------- 42 | ###----------------------------------------------------------- 43 | ### utilities 44 | ###----------------------------------------------------------- 45 | 46 | def get_cookie_links( cfg ) 47 | cfg.xpath( "apply-macro[name = 'netdev_lag[:links]']/data/name" ).collect { |n| n.text } 48 | end 49 | 50 | def set_cookie_links( cfg ) 51 | cfg.send(:'apply-macro', Netconf::JunosConfig::REPLACE ) { 52 | cfg.name 'netdev_lag[:links]' 53 | should[:links].each{ |ifd| 54 | cfg.data { cfg.name ifd } 55 | } 56 | } 57 | end 58 | 59 | ### --------------------------------------------------------------- 60 | ### XML property readers 61 | ### --------------------------------------------------------------- 62 | 63 | def xml_config_read! 64 | database = {'database' => 'committed'} 65 | @ndev.rpc.get_configuration(xml_at_top, database) 66 | end 67 | 68 | def xml_get_has_xml( xml ) 69 | if ndev.facts[:ifd_style] == "CLASSIC" 70 | @ifd_ether_options = 'gigether-options' 71 | else 72 | @ifd_ether_options = 'ether-options' 73 | end 74 | xml.xpath('//interface')[0] 75 | end 76 | 77 | def xml_read_parser( as_xml, as_hash ) 78 | set_has_status( as_xml, as_hash ) 79 | 80 | # property :links 81 | ae_name = as_xml.xpath('name').text 82 | as_hash[:links] = Set.new(get_cookie_links(as_xml)) 83 | 84 | # property :lacp 85 | ae_opts = as_xml.xpath('aggregated-ether-options') 86 | if (lacp = ae_opts.xpath('lacp')[0]) 87 | as_hash[:lacp] = (lacp.xpath('active')[0]) ? :active : :passive 88 | else 89 | as_hash[:lacp] = :disabled 90 | end 91 | 92 | # property :minimum_links 93 | as_hash[:minimum_links] = (min_links = ae_opts.xpath('minimum-links')[0]) ? min_links.text.to_i : 1 94 | end 95 | 96 | ### --------------------------------------------------------------- 97 | ### XML property writers 98 | ### --------------------------------------------------------------- 99 | def update_ifd_should() 100 | if @should[:links].empty? 101 | raise Junos::Ez::NoProviderError, "\n *links* are compulsory for creating lag interface!!! \n" 102 | else 103 | ether_option = @should[:links][0].to_s 104 | @ifd_ether_options = (ether_option.start_with? 'fe-') ? 'fastether-options' : 'gigether-options' 105 | end 106 | end 107 | 108 | def update_ifd_has() 109 | @has[:links] = @has[:links].to_a 110 | if @has[:links].empty? 111 | raise Junos::Ez::NoProviderError, "\n Either lag interface is not created or links associated with given lag interface is not supported \n" 112 | else 113 | ether_option = @has[:links][0].to_s 114 | @ifd_ether_options = (ether_option.start_with? 'fe-') ? 'fastether-options' : 'gigether-options' 115 | end 116 | end 117 | 118 | def xml_change_links( xml ) 119 | update_ifd_should() 120 | @should[:links] = @should[:links].to_set if @should[:links].kind_of? Array 121 | 122 | has = @has[:links] || Set.new 123 | should = @should[:links] || Set.new 124 | 125 | set_cookie_links( xml ) 126 | 127 | del = has - should 128 | add = should - has 129 | 130 | par = xml.instance_variable_get(:@parent) 131 | dot_ifd = par.at_xpath('ancestor::interfaces') 132 | 133 | add.each{ |new_ifd| Nokogiri::XML::Builder.with( dot_ifd ) {|dot| 134 | dot.interface { dot.name new_ifd 135 | dot.send(@ifd_ether_options.to_sym) { 136 | dot.send(:'ieee-802.3ad') { 137 | dot.bundle @name 138 | } 139 | } 140 | }}} 141 | 142 | del.each{ |new_ifd| Nokogiri::XML::Builder.with( dot_ifd ) {|dot| 143 | dot.interface { dot.name new_ifd 144 | dot.send(@ifd_ether_options) { 145 | dot.send( :'ieee-802.3ad', Netconf::JunosConfig::DELETE ) 146 | } 147 | }}} 148 | end 149 | 150 | def xml_change_lacp( xml ) 151 | if @should[:lacp] == :disabled or @should[:lacp].nil? 152 | xml.send(:'aggregated-ether-options') { 153 | xml.lacp( Netconf::JunosConfig::DELETE ) 154 | } 155 | else 156 | xml.send(:'aggregated-ether-options') { 157 | xml.lacp { xml.send @should[:lacp] } # @@@ should validate :lacp value before doing this... 158 | } 159 | end 160 | end 161 | 162 | def xml_change_minimum_links( xml ) 163 | if @should[:minimum_links] 164 | xml.send(:'aggregated-ether-options') { 165 | xml.send( :'minimum-links', @should[:minimum_links] ) 166 | } 167 | else 168 | xml.send(:'aggregated-ether-options') { 169 | xml.send(:'minimum-links', Netconf::JunosConfig::DELETE ) 170 | } 171 | end 172 | end 173 | 174 | ### --------------------------------------------------------------- 175 | ### XML on-create 176 | ### --------------------------------------------------------------- 177 | 178 | def xml_on_create( xml ) 179 | # make sure there is a 'unit 0' on the AE port 180 | par = xml.instance_variable_get(:@parent) 181 | Nokogiri::XML::Builder.with(par) do |dot| 182 | dot.unit { 183 | dot.name '0' 184 | } 185 | end 186 | end 187 | 188 | ### --------------------------------------------------------------- 189 | ### XML on-delete 190 | ### --------------------------------------------------------------- 191 | 192 | def xml_on_delete( xml ) 193 | update_ifd_has() 194 | par = xml.instance_variable_get(:@parent) 195 | dot_ifd = par.at_xpath('ancestor::interfaces') 196 | 197 | # remove the bindings from each of the physical interfaces 198 | # 199 | @has[:links].each do |new_ifd| Nokogiri::XML::Builder.with( dot_ifd ) do |dot| 200 | dot.interface { dot.name new_ifd 201 | dot.send(@ifd_ether_options) { 202 | dot.send( :'ieee-802.3ad', Netconf::JunosConfig::DELETE ) 203 | } 204 | } 205 | end 206 | end 207 | 208 | # now remove the LAG interface 209 | # 210 | Nokogiri::XML::Builder.with( dot_ifd ) do |dot| 211 | dot.interface( Netconf::JunosConfig::DELETE ) { 212 | dot.name @name 213 | } 214 | end 215 | end 216 | 217 | end 218 | 219 | 220 | ##### --------------------------------------------------------------- 221 | ##### Provider collection methods 222 | ##### --------------------------------------------------------------- 223 | 224 | class Junos::Ez::LAGports::Provider 225 | 226 | def build_list 227 | @ndev.rpc.get_interface_information( 228 | :terse => true, 229 | :interface_name => 'ae*' 230 | ).xpath('physical-interface/name').collect{ |name| name.text.strip } 231 | end 232 | 233 | def build_catalog 234 | return @catalog if list!.empty? 235 | 236 | list.each do |ae_name| 237 | @ndev.rpc.get_configuration{ |xml| 238 | xml.interfaces { 239 | xml.interface { 240 | xml.name ae_name 241 | } 242 | } 243 | }.xpath('interfaces/interface').each do |as_xml| 244 | @catalog[ae_name] = {} 245 | xml_read_parser( as_xml, @catalog[ae_name] ) 246 | end 247 | end 248 | 249 | @catalog 250 | end 251 | 252 | end 253 | 254 | ##### --------------------------------------------------------------- 255 | ##### _PRIVATE methods 256 | ##### --------------------------------------------------------------- 257 | 258 | class Junos::Ez::LAGports::Provider 259 | def _get_port_list( name ) 260 | @ndev.rpc.get_interface_information( 261 | :detail => true, 262 | :interface_name => name + '.0' 263 | ).xpath('//lag-link/name').collect{ |name| 264 | name.text.strip.split('.',2).first 265 | } 266 | end 267 | end 268 | 269 | -------------------------------------------------------------------------------- /lib/junos-ez/stdlib.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'junos-ez/provider' # framework code 3 | require 'junos-ez/facts' # fact keeper 4 | require 'junos-ez/system' # various system resources 5 | require 'junos-ez/l1_ports' # physical ports 6 | require 'junos-ez/vlans' # vlans 7 | require 'junos-ez/l2_ports' # switch ports 8 | require 'junos-ez/ip_ports' # ip ports (v4) 9 | require 'junos-ez/lag_ports' # Link Aggregation Groups 10 | require 'junos-ez/group' 11 | 12 | # ------------------------------------------------------------------- 13 | # utility libraries, not providers 14 | # ------------------------------------------------------------------- 15 | 16 | require 'junos-ez/utils/re' 17 | require 'junos-ez/utils/fs' 18 | require 'junos-ez/utils/config' 19 | -------------------------------------------------------------------------------- /lib/junos-ez/system.rb: -------------------------------------------------------------------------------- 1 | 2 | require "junos-ez/provider" 3 | require 'junos-ez/system/st_hosts' 4 | require 'junos-ez/system/st_routes' 5 | require 'junos-ez/system/users' 6 | require 'junos-ez/system/userauths' 7 | 8 | ### ----------------------------------------------------------------- 9 | ### the 'syscfg' is a work in progress, do not use ... 10 | ### ----------------------------------------------------------------- 11 | 12 | module Junos::Ez::SysConfig 13 | 14 | PROPERTIES = [ 15 | :host_name, # String, host-name 16 | :domain_name, # domain name, string or array 17 | :domain_search, # array of dns name suffix values 18 | :dns_servers, # array of ip-addrs 19 | :ntp_servers, # array NTP servers HASH of 20 | # :version 21 | # :key 22 | :timezone, # String time-zone 23 | :date, # String format: YYYYMMDDhhmm.ss 24 | :location, # location HASH with properties 25 | # :countrycode 26 | # :building, 27 | # :floor, 28 | # :rack 29 | ] 30 | 31 | def self.Provider( ndev, varsym ) 32 | raise ArgumentError "work-in-progress ..." 33 | 34 | newbie = Junos::Ez::SysConfig::Provider.new( ndev ) 35 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 36 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 37 | end 38 | 39 | class Provider < Junos::Ez::Provider::Parent 40 | end 41 | 42 | end 43 | 44 | require 'junos-ez/system/syscfg' 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/junos-ez/system/st_hosts.rb: -------------------------------------------------------------------------------- 1 | module Junos::Ez::StaticHosts 2 | 3 | PROPERTIES = [ 4 | :ip, # ipv4 address :String 5 | :ip6, # ipv6 address :String 6 | ] 7 | 8 | def self.Provider( ndev, varsym ) 9 | newbie = Junos::Ez::StaticHosts::Provider.new( ndev ) 10 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 11 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 12 | end 13 | 14 | class Provider < Junos::Ez::Provider::Parent 15 | end 16 | 17 | end 18 | 19 | class Junos::Ez::StaticHosts::Provider 20 | 21 | ### --------------------------------------------------------------- 22 | ### XML top placement 23 | ### --------------------------------------------------------------- 24 | 25 | def xml_at_top 26 | xml = Nokogiri::XML::Builder.new {|xml| xml.configuration { 27 | xml.system { xml.send('static-host-mapping') { 28 | xml.name @name 29 | return xml 30 | }} 31 | }} 32 | end 33 | 34 | ### --------------------------------------------------------------- 35 | ### XML property readers 36 | ### --------------------------------------------------------------- 37 | 38 | def xml_get_has_xml( xml ) 39 | xml.xpath('//static-host-mapping')[0] 40 | end 41 | 42 | def xml_read_parser( as_xml, as_hash ) 43 | set_has_status( as_xml, as_hash ) 44 | 45 | ip_v4 = as_xml.xpath('inet').text 46 | as_hash[:ip] = ip_v4 unless ip_v4.empty? 47 | 48 | ip_v6 = as_xml.xpath('inet6').text 49 | as_hash[:ip6] = ip_v6 unless ip_v6.empty? 50 | end 51 | 52 | ### --------------------------------------------------------------- 53 | ### XML property writers 54 | ### --------------------------------------------------------------- 55 | 56 | def xml_change_ip( xml ) 57 | xml_set_or_delete( xml, 'inet', @should[:ip] ) 58 | end 59 | 60 | def xml_change_ip6( xml ) 61 | xml_set_or_delete( xml, 'inet6', @should[:ip6] ) 62 | end 63 | 64 | end 65 | 66 | ##### --------------------------------------------------------------- 67 | ##### Provider collection methods 68 | ##### --------------------------------------------------------------- 69 | 70 | class Junos::Ez::StaticHosts::Provider 71 | 72 | def build_list 73 | @ndev.rpc.get_configuration{|xml| xml.system { 74 | xml.send(:'static-host-mapping') 75 | }}.xpath('system/static-host-mapping/name').collect do |item| 76 | item.text 77 | end 78 | end 79 | 80 | def build_catalog 81 | @catalog = {} 82 | @ndev.rpc.get_configuration{ |xml| xml.system { 83 | xml.send(:'static-host-mapping') 84 | }}.xpath('system/static-host-mapping').each do |item| 85 | name = item.xpath('name').text 86 | @catalog[name] = {} 87 | xml_read_parser( item, @catalog[name] ) 88 | end 89 | @catalog 90 | end 91 | 92 | end 93 | -------------------------------------------------------------------------------- /lib/junos-ez/system/st_routes.rb: -------------------------------------------------------------------------------- 1 | module Junos::Ez::StaticRoutes 2 | 3 | PROPERTIES = [ 4 | :gateway, # next-hop gateway, could be single or Array 5 | :metric, # number or nil 6 | :action, # one-of [ :reject, :discard, :receive ] 7 | :active, # flag [ true, nil | false ] 8 | :retain, # no-flag [ nil, true, false ] 9 | :install, # no-flag [ nil, true, false ] 10 | :readvertise, # no-flag [ nil, true, false ] 11 | :resolve, # no-flag [ nil, true, false ] 12 | ] 13 | 14 | def self.Provider( ndev, varsym ) 15 | newbie = Junos::Ez::StaticRoutes::Provider.new( ndev ) 16 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 17 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 18 | end 19 | 20 | class Provider < Junos::Ez::Provider::Parent 21 | end 22 | 23 | end 24 | 25 | class Junos::Ez::StaticRoutes::Provider 26 | 27 | ### --------------------------------------------------------------- 28 | ### XML top placement 29 | ### --------------------------------------------------------------- 30 | 31 | def xml_at_top 32 | @name = "0.0.0.0/0" if @name == :default 33 | 34 | Nokogiri::XML::Builder.new {|xml| xml.configuration { 35 | xml.send('routing-options') { 36 | xml.static { xml.route { 37 | xml.name @name 38 | return xml 39 | }} 40 | } 41 | }} 42 | end 43 | 44 | ### --------------------------------------------------------------- 45 | ### XML property readers 46 | ### --------------------------------------------------------------- 47 | 48 | def xml_get_has_xml( xml ) 49 | xml.xpath('routing-options/static/route')[0] 50 | end 51 | 52 | def xml_read_parser( as_xml, as_hash ) 53 | set_has_status( as_xml, as_hash ) 54 | 55 | ## :gateway 56 | unless (next_hop = as_xml.xpath('next-hop')).empty? 57 | if next_hop.count == 1 58 | as_hash[:gateway] = next_hop.text 59 | else 60 | as_hash[:gateway] = next_hop.collect{|i| i.text } 61 | end 62 | end 63 | 64 | unless (active = as_xml.xpath('active')).empty? 65 | as_hash[:active] = true 66 | end 67 | 68 | unless (action = as_xml.xpath( 'reject | discard | receive' )).empty? 69 | as_hash[:action] = action[0].name.to_sym 70 | end 71 | 72 | unless (metric = as_xml.xpath('metric')).empty? 73 | as_hash[:metric] = metric.text.to_i 74 | end 75 | 76 | xml_read_parse_noele( as_xml, 'retain', as_hash, :retain ) 77 | xml_read_parse_noele( as_xml, 'install', as_hash, :install ) 78 | xml_read_parse_noele( as_xml, 'resolve', as_hash, :resolve ) 79 | xml_read_parse_noele( as_xml, 'readvertise', as_hash, :readvertise ) 80 | end 81 | 82 | 83 | ### --------------------------------------------------------------- 84 | ### XML property writers 85 | ### --------------------------------------------------------------- 86 | 87 | def xml_change_action( xml ) 88 | 89 | if @should[:action].nil? 90 | xml.send( @has[:action], Netconf::JunosConfig::DELETE ) 91 | return true 92 | end 93 | 94 | xml.send( @should[:action] ) 95 | end 96 | 97 | def xml_change_active( xml ) 98 | xml_set_or_delete_element( xml, 'active', @should[:active] ) 99 | end 100 | 101 | def xml_change_gateway( xml ) 102 | # delete existing entries 103 | ele_nh = :'next-hop' 104 | 105 | # clear any existing values, and return unless there are any new ones ... 106 | xml.send(ele_nh, Netconf::JunosConfig::DELETE) if @has[:gateway] 107 | return true unless @should[:gateway] 108 | 109 | ## adding back the ones we want now ... 110 | if @should[:gateway].kind_of? String 111 | xml.send( ele_nh, @should[:gateway] ) 112 | else 113 | @should[:gateway].each{ |gw| xml.send( ele_nh, gw ) } 114 | end 115 | end 116 | 117 | def xml_change_retain( xml ) 118 | xml_set_or_delete_noele( xml, 'retain' ) 119 | end 120 | 121 | def xml_change_install( xml ) 122 | xml_set_or_delete_noele( xml, 'install' ) 123 | end 124 | 125 | def xml_change_resolve( xml ) 126 | xml_set_or_delete_noele( xml, 'resolve' ) 127 | end 128 | 129 | def xml_change_readvertise( xml ) 130 | xml_set_or_delete_noele( xml, 'readvertise' ) 131 | end 132 | 133 | def xml_change_metric( xml ) 134 | xml_set_or_delete( xml, 'metric', @should[:metric] ) 135 | end 136 | 137 | 138 | end 139 | 140 | ##### --------------------------------------------------------------- 141 | ##### Provider collection methods 142 | ##### --------------------------------------------------------------- 143 | 144 | class Junos::Ez::StaticRoutes::Provider 145 | 146 | def build_list 147 | @ndev.rpc.get_configuration{|xml| xml.send(:'routing-options') { 148 | xml.static { xml.route } 149 | }}.xpath('//route/name').collect do |item| 150 | item.text 151 | end 152 | end 153 | 154 | def build_catalog 155 | @catalog = {} 156 | @catalog 157 | end 158 | 159 | end 160 | -------------------------------------------------------------------------------- /lib/junos-ez/system/syscfg.rb: -------------------------------------------------------------------------------- 1 | class Junos::Ez::SysConfig::Provider 2 | 3 | ### --------------------------------------------------------------- 4 | ### XML top placement 5 | ### --------------------------------------------------------------- 6 | 7 | def xml_at_top 8 | xml = Nokogiri::XML::Builder.new {|xml| xml.configuration { 9 | xml.system { 10 | return xml 11 | } 12 | }} 13 | end 14 | 15 | def xml_config_read! 16 | xml = xml_at_top 17 | xml.send(:'host-name') 18 | xml.send(:'domain-name') 19 | xml.send(:'domain-search') 20 | xml.send(:'time-zone') 21 | xml.location 22 | xml.send(:'name-server') 23 | xml.ntp 24 | @ndev.rpc.get_configuration( xml ) 25 | end 26 | 27 | ### --------------------------------------------------------------- 28 | ### XML property readers 29 | ### --------------------------------------------------------------- 30 | 31 | def xml_get_has_xml( xml ) 32 | xml.xpath('system')[0] 33 | end 34 | 35 | def xml_read_parser( as_xml, as_hash ) 36 | set_has_status( as_xml, as_hash ) 37 | as_hash[:host_name] = as_xml.xpath('host-name').text 38 | unless (data = as_xml.xpath('domain-name')).empty? 39 | as_hash[:domain_name] = data.text 40 | end 41 | unless (data = as_xml.xpath('domain-search')).empty? 42 | as_hash[:domain_search] = data.collect{|i| i.text} 43 | end 44 | unless (data = as_xml.xpath('time-zone')).empty? 45 | as_hash[:timezone] = data.text 46 | end 47 | unless (data = as_xml.xpath('name-server/name')).empty? 48 | as_hash[:dns_servers] = data.collect{|i| i.text} 49 | end 50 | unless (data = as_xml.xpath('ntp/server/name')).empty? 51 | as_hash[:ntp_servers] = data.collect{|i| i.text} 52 | end 53 | unless (location = as_xml.xpath('location')).empty? 54 | as_hash[:location] = {} 55 | unless (data = location.xpath('building')).empty? 56 | as_hash[:location][:building] = data.text 57 | end 58 | unless (data = location.xpath('country-code')).empty? 59 | as_hash[:location][:countrycode] = data.text 60 | end 61 | unless (data = location.xpath('floor')).empty? 62 | as_hash[:location][:floor] = data.text 63 | end 64 | unless (data = location.xpath('rack')).empty? 65 | as_hash[:location][:rack] = data.text 66 | end 67 | end 68 | end 69 | 70 | ### --------------------------------------------------------------- 71 | ### XML property writers 72 | ### --------------------------------------------------------------- 73 | 74 | def xml_change_host_name( xml ) 75 | xml.send(:'host-name', @should[:host_name] ) 76 | end 77 | 78 | def xml_change_domain_name( xml ) 79 | xml.send(:'domain-name', @should[:domain_name] ) 80 | end 81 | 82 | def xml_change_domain_search( xml ) 83 | end 84 | 85 | def xml_change_timezone( xml ) 86 | xml.send(:'time-zone', @should[:timezone]) 87 | end 88 | 89 | def xml_change_dns_servers( xml ) 90 | end 91 | 92 | def xml_change_ntp_servers( xml ) 93 | end 94 | 95 | def xml_change_date( xml ) 96 | end 97 | 98 | def xml_change_location( xml ) 99 | end 100 | 101 | end 102 | 103 | 104 | -------------------------------------------------------------------------------- /lib/junos-ez/system/userauths.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | =end 3 | 4 | module Junos::Ez::UserAuths 5 | 6 | VALID_KEY_TYPES = ['ssh-rsa','ssh-dsa'] 7 | 8 | def self.Provider( ndev, varsym ) 9 | newbie = Junos::Ez::UserAuths::Provider.new( ndev ) 10 | newbie.properties = Junos::Ez::Provider::PROPERTIES 11 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 12 | end 13 | 14 | class Provider < Junos::Ez::Provider::Parent 15 | end 16 | 17 | end 18 | 19 | ##### --------------------------------------------------------------- 20 | ##### Resource Property Methods 21 | ##### --------------------------------------------------------------- 22 | 23 | class Junos::Ez::UserAuths::Provider 24 | 25 | ### --------------------------------------------------------------- 26 | ### XML top placement 27 | ### --------------------------------------------------------------- 28 | 29 | def xml_at_top 30 | Nokogiri::XML::Builder.new{|x| x.configuration{ 31 | x.system { x.login { x.user { 32 | x.name @name[:user] 33 | x.authentication { 34 | x.send( @name[:keytype].to_sym ) { 35 | x.name @name[:publickey] 36 | return x 37 | } 38 | } 39 | }}} 40 | }} 41 | end 42 | 43 | ### --------------------------------------------------------------- 44 | ### XML readers 45 | ### --------------------------------------------------------------- 46 | 47 | def xml_get_has_xml( xml ) 48 | @should[:_active] = true # mark it so it will write! 49 | xml.xpath('//user/authentication/*')[0] 50 | end 51 | 52 | def xml_read_parser( as_xml, as_hash ) 53 | set_has_status( as_xml, as_hash ) 54 | end 55 | 56 | ### --------------------------------------------------------------- 57 | ### XML writers 58 | ### --------------------------------------------------------------- 59 | 60 | ## !! since we're not actually modifying any properties, we need 61 | ## !! to overload the xml_build_change method to simply return 62 | ## !! the config at-top (includes ssh name) 63 | 64 | def xml_build_change( xml_at_here = nil ) 65 | xml_at_top.doc.root 66 | end 67 | 68 | end 69 | 70 | ##### --------------------------------------------------------------- 71 | ##### Provider Collection Methods 72 | ##### --------------------------------------------------------------- 73 | 74 | class Junos::Ez::UserAuths::Provider 75 | def build_list 76 | [] 77 | end 78 | 79 | def build_catalog 80 | {} 81 | end 82 | end 83 | 84 | 85 | -------------------------------------------------------------------------------- /lib/junos-ez/system/users.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | =end 3 | 4 | require 'junos-ez/system/userauths' 5 | 6 | module Junos::Ez::Users 7 | 8 | PROPERTIES = [ 9 | :uid, # User-ID, Number 10 | :class, # User Class, String 11 | :fullname, # Full Name, String 12 | :password, # Encrypted password 13 | :ssh_keys, # READ-ONLY, Hash of SSH public keys 14 | ] 15 | 16 | def self.Provider( ndev, varsym ) 17 | newbie = Junos::Ez::Users::Provider.new( ndev ) 18 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 19 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 20 | end 21 | 22 | class Provider < Junos::Ez::Provider::Parent; end 23 | 24 | end 25 | 26 | 27 | ##### --------------------------------------------------------------- 28 | ##### Provider Resource Methods 29 | ##### --------------------------------------------------------------- 30 | 31 | class Junos::Ez::Users::Provider 32 | 33 | ### --------------------------------------------------------------- 34 | ### XML top placement 35 | ### --------------------------------------------------------------- 36 | 37 | def xml_at_top 38 | Nokogiri::XML::Builder.new{|x| x.configuration{ 39 | x.system { x.login { x.user { 40 | x.name @name 41 | return x 42 | }}}}} 43 | end 44 | 45 | ### --------------------------------------------------------------- 46 | ### XML readers 47 | ### --------------------------------------------------------------- 48 | 49 | def xml_get_has_xml( xml ) 50 | xml.xpath('//user')[0] 51 | end 52 | 53 | def xml_read_parser( as_xml, as_hash ) 54 | set_has_status( as_xml, as_hash ) 55 | 56 | as_hash[:uid] = as_xml.xpath('uid').text 57 | as_hash[:class] = as_xml.xpath('class').text 58 | 59 | xml_when_item(as_xml.xpath('full-name')) {|i| 60 | as_hash[:fullname] = i.text 61 | } 62 | 63 | xml_when_item(as_xml.xpath('authentication/encrypted-password')) {|i| 64 | as_hash[:password] = i.text 65 | } 66 | 67 | # READ-ONLY capture the keys 68 | unless (keys = as_xml.xpath('authentication/ssh-rsa')).empty? 69 | as_hash[:ssh_keys] ||= {} 70 | as_hash[:ssh_keys]['ssh-rsa'] = keys.collect{|key| key.text.strip} 71 | end 72 | unless (keys = as_xml.xpath('authentication/ssh-dsa')).empty? 73 | as_hash[:ssh_keys] ||= {} 74 | as_hash[:ssh_keys]['ssh-dsa'] = keys.collect{|key| key.text.strip} 75 | end 76 | end 77 | 78 | ### --------------------------------------------------------------- 79 | ### XML writers 80 | ### --------------------------------------------------------------- 81 | 82 | def xml_change_password( xml ) 83 | xml.authentication { 84 | xml_set_or_delete( xml, 'encrypted-password', @should[:password] ) 85 | } 86 | end 87 | 88 | def xml_change_fullname( xml ) 89 | xml_set_or_delete( xml, 'full-name', @should[:fullname] ) 90 | end 91 | 92 | # changing the 'gid' is changing the Junos 'class' element 93 | # so, what is tough here is that the Nokogiri Builder mech 94 | # won't allow us to use the string 'class' since it conflicts 95 | # with the Ruby language. So we need to add the 'class' element 96 | # the hard way, yo! ... 97 | 98 | def xml_change_class( xml ) 99 | par = xml.instance_variable_get(:@parent) 100 | doc = xml.instance_variable_get(:@doc) 101 | user_class = Nokogiri::XML::Node.new('class', doc ) 102 | user_class.content = @should[:class] 103 | par.add_child( user_class ) 104 | end 105 | 106 | def xml_change_uid( xml ) 107 | xml_set_or_delete( xml, 'uid', @should[:uid] ) 108 | end 109 | 110 | end 111 | 112 | ##### --------------------------------------------------------------- 113 | ##### Provider Collection Methods 114 | ##### --------------------------------------------------------------- 115 | 116 | class Junos::Ez::Users::Provider 117 | 118 | def build_list 119 | @ndev.rpc.get_configuration{ |x| x.system { 120 | x.login { 121 | x.user({:recurse => 'false'}) 122 | } 123 | }} 124 | .xpath('//user/name').collect{ |i| i.text } 125 | end 126 | 127 | def build_catalog 128 | @catalog = {} 129 | @ndev.rpc.get_configuration{ |x| x.system { 130 | x.login 131 | }} 132 | .xpath('//user').each do |user| 133 | name = user.xpath('name').text 134 | @catalog[name] = {} 135 | xml_read_parser( user, @catalog[name] ) 136 | end 137 | @catalog 138 | end 139 | 140 | end 141 | 142 | ##### --------------------------------------------------------------- 143 | ##### Resource Methods 144 | ##### --------------------------------------------------------------- 145 | 146 | class Junos::Ez::Users::Provider 147 | 148 | ## ---------------------------------------------------------------- 149 | ## change the password by providing it in plain-text 150 | ## ---------------------------------------------------------------- 151 | 152 | def password=(plain_text) 153 | xml = xml_at_top 154 | xml.authentication { 155 | xml.send(:'plain-text-password-value', plain_text) 156 | } 157 | @ndev.rpc.load_configuration( xml ) 158 | return true 159 | end 160 | 161 | ## ---------------------------------------------------------------- 162 | ## get a Hash that is used as the 'name' for obtaining a resource 163 | ## for Junos::Ez::UserAuths::Provider 164 | ## ---------------------------------------------------------------- 165 | 166 | def ssh_key( keytype, index = 0 ) 167 | return nil unless @has[:ssh_keys] 168 | return nil unless @has[:ssh_keys][keytype] 169 | ret_h = {:user => @name, :keytype => keytype} 170 | ret_h[:publickey] = @has[:ssh_keys][keytype][index] 171 | ret_h 172 | end 173 | 174 | ## 175 | ## @@ need to move this code into the main provider 176 | ## @@ as a utility ... 177 | ## 178 | 179 | def get_userauth_provd 180 | @ndev.providers.each do |p| 181 | obj = @ndev.send(p) 182 | return obj if obj.class == Junos::Ez::UserAuths::Provider 183 | end 184 | end 185 | 186 | ## ---------------------------------------------------------------- 187 | ## load an SSH public key & return the resulting key object. 188 | ## You can provide the publickey either as :publickey or 189 | ## contents will be read from :filename 190 | ## ---------------------------------------------------------------- 191 | 192 | def load_ssh_key!( opts = {} ) 193 | publickey = opts[:publickey] || File.read( opts[:filename] ).strip 194 | raise ArgumentError, "no public-key specified" unless publickey 195 | 196 | # nab the provider for handling ssh-keys, since we'll use that 197 | # for key resource management 198 | 199 | @auth_provd ||= get_userauth_provd 200 | raise StandardError, "No Junos::Ez::UserAuths::Provider" unless @auth_provd 201 | 202 | # extract the key-type from the public key. 203 | keytype = publickey[0..6] 204 | keytype = 'ssh-dsa' if keytype == 'ssh-dss' 205 | raise ArgumentError, "Unknown ssh key-type #{keytype}" unless Junos::Ez::UserAuths::VALID_KEY_TYPES.include? keytype 206 | 207 | # ok, we've got everything we need to add the key, so here we go. 208 | key_name = {:user => @name, :keytype => keytype, :publickey => publickey } 209 | key = @auth_provd[ key_name ] 210 | key.write! 211 | 212 | # return the key in case the caller wants it 213 | key 214 | end 215 | 216 | end 217 | 218 | -------------------------------------------------------------------------------- /lib/junos-ez/utils/config.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | --------------------------------------------------------------------- 3 | Config::Utils is a collection of methods used for loading 4 | configuration files/templates and software images 5 | 6 | commit! - commit configuration 7 | commit? - see if a candidate config is OK (commit-check) 8 | diff? - shows the diff of the candidate config w/current | rolback 9 | load! - load configuration onto device 10 | lock! - take exclusive lock on config 11 | unlock! - release exclusive lock on config 12 | rollback! - perform a config rollback 13 | get_config - returns requested config in "text" format-style 14 | 15 | --------------------------------------------------------------------- 16 | =end 17 | 18 | module Junos::Ez::Config 19 | def self.Utils( ndev, varsym ) 20 | newbie = Junos::Ez::Config::Provider.new( ndev ) 21 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 22 | end 23 | end 24 | 25 | ### ----------------------------------------------------------------- 26 | ### PUBLIC METHODS 27 | ### ----------------------------------------------------------------- 28 | ### ----------------------------------------------------------------- 29 | 30 | class Junos::Ez::Config::Provider < Junos::Ez::Provider::Parent 31 | 32 | ### --------------------------------------------------------------- 33 | ### load! - used to load configuration files / templates. This 34 | ### does not perform a 'commit', just the equivalent of the 35 | ### load-configuration RPC 36 | ### 37 | ### --- options --- 38 | ### 39 | ### :filename => path - indcates the filename of content 40 | ### note: filename extension will also define format 41 | ### .{conf,text,txt} <==> :text 42 | ### .xml <==> :xml 43 | ### .set <==> :set 44 | ### 45 | ### :content => String - string content of data (vs. :filename) 46 | ### 47 | ### :format => [:text, :set, :xml], default :text (curly-brace) 48 | ### this will override any auto-format from the :filename 49 | ### 50 | ### :binding - indicates file/content is an ERB 51 | ### => - will grab the binding from this object 52 | ### using a bit of meta-programming magic 53 | ### => - will use this binding 54 | ### 55 | ### :replace! => true - enables the 'replace' option 56 | ### :overwrite! => true - enables the 'overwrite' optoin 57 | ### 58 | ### --- returns --- 59 | ### true if the configuration is loaded OK 60 | ### raise Netconf::EditError otherwise 61 | ### --------------------------------------------------------------- 62 | 63 | def load!( opts = {} ) 64 | raise ArgumentError unless opts[:content] || opts[:filename] 65 | 66 | content = opts[:content] || File.read( opts[:filename] ) 67 | 68 | attrs = {} 69 | attrs[:action] = 'replace' if opts[:replace!] 70 | attrs[:action] = 'override' if opts[:override!] 71 | 72 | if opts[:format] 73 | attrs[:format] = opts[:format].to_s 74 | elsif opts[:filename] 75 | case f_ext = File.extname( opts[:filename] ) 76 | when '.conf','.text','.txt'; attrs[:format] = 'text' 77 | when '.set'; attrs[:format] = 'set' 78 | when '.xml'; # default is XML 79 | else 80 | raise ArgumentError, "unknown format from extension: #{f_ext}" 81 | end 82 | else 83 | raise ArgumentError "unspecified format" 84 | end 85 | 86 | if opts[:binding] 87 | erb = ERB.new( content, nil, '>' ) 88 | case opts[:binding] 89 | when Binding 90 | # binding was provided to use 91 | content = erb.result( opts[:binding] ) 92 | when Object 93 | obj = opts[:binding] 94 | def obj.junos_ez_binding; binding end 95 | content = erb.result( obj.junos_ez_binding ) 96 | class << obj; remove_method :junos_ez_binding end 97 | end 98 | end 99 | 100 | @ndev.rpc.load_configuration( content, attrs ) 101 | true # everthing OK! 102 | end 103 | 104 | ### --------------------------------------------------------------- 105 | ### commit! - commits the configuration to the device 106 | ### 107 | ### --- options --- 108 | ### 109 | ### :confirm => true | timeout 110 | ### :comment => commit log comment 111 | ### 112 | ### --- returns --- 113 | ### true if commit completed 114 | ### raises Netconf::CommitError otherwise 115 | ### --------------------------------------------------------------- 116 | 117 | def commit!( opts = {} ) 118 | 119 | args = {} 120 | args[:log] = opts[:comment] if opts[:comment] 121 | if opts[:confirm] 122 | args[:confirmed] = true 123 | if opts[:confirm] != true 124 | timeout = Integer( opts[:confirm] ) rescue false 125 | raise ArgumentError "invalid timeout #{opts[:confirm]}" unless timeout 126 | args[:confirm_timeout] = timeout 127 | end 128 | end 129 | 130 | @ndev.rpc.commit_configuration( args ) 131 | true 132 | end 133 | 134 | ### --------------------------------------------------------------- 135 | ### commit? - perform commit configuration check 136 | ### 137 | ### --- returns --- 138 | ### true if candidate config is OK to commit 139 | ### Array of rpc-error data otherwise 140 | ### --------------------------------------------------------------- 141 | 142 | def commit? 143 | begin 144 | @ndev.rpc.commit_configuration( :check => true ) 145 | rescue => e 146 | return Junos::Ez::rpc_errors( e.rsp ) 147 | end 148 | true # commit check OK! 149 | end 150 | 151 | ### --------------------------------------------------------------- 152 | ### rollback! - used to rollback the configuration 153 | ### --------------------------------------------------------------- 154 | 155 | def rollback!( rollback_id = 0 ) 156 | raise ArgumentError, "invalid rollback #{rollback_id}" unless ( rollback_id >= 0 and rollback_id <= 50 ) 157 | @ndev.rpc.load_configuration( :compare=>'rollback', :rollback=> rollback_id.to_s ) 158 | true # rollback OK! 159 | end 160 | 161 | ### --------------------------------------------------------------- 162 | ### diff? - displays diff (patch format) between 163 | ### current candidate configuration loaded and the rollback_id 164 | ### 165 | ### --- returns --- 166 | ### nil if no diff 167 | ### String of diff output otherwise 168 | ### --------------------------------------------------------------- 169 | 170 | def diff?( rollback_id = 0 ) 171 | raise ArgumentError, "invalid rollback #{rollback_id}" unless ( rollback_id >= 0 and rollback_id <= 50 ) 172 | got = ndev.rpc.get_configuration( :compare => 'rollback', :rollback => rollback_id.to_s, :format => 'text' ) 173 | diff = got.xpath('configuration-output').text 174 | return nil if diff == "\n" 175 | diff 176 | end 177 | 178 | ### --------------------------------------------------------------- 179 | ### lock! - takes an exclusive lock on the candidate config 180 | ### 181 | ### --- returns --- 182 | ### true if lock acquired 183 | ### raise Netconf::LockError otherwise 184 | ### --------------------------------------------------------------- 185 | 186 | def lock! 187 | @ndev.rpc.lock_configuration 188 | true 189 | end 190 | 191 | ### --------------------------------------------------------------- 192 | ### unlock! - releases exclusive lock on candidate config 193 | ### 194 | ### --- returns --- 195 | ### true if lock release 196 | ### raise Netconf::RpcError otherwise 197 | ### --------------------------------------------------------------- 198 | 199 | def unlock! 200 | @ndev.rpc.unlock_configuration 201 | true 202 | end 203 | 204 | ### --------------------------------------------------------------- 205 | ### get_config - returns String of requested (or entire) config 206 | ### in "text" (curly-brace) format. The 'rqst' argument 207 | ### identifies the scope of the config, for example: 208 | ### 209 | ### .get_config( "interfaces ge-0/0/0" ) 210 | ### 211 | ### If there is no configuration available, 'nil' is returned 212 | ### 213 | ### If there is an error in the request, that will be returned 214 | ### as a String with "ERROR!" prepended 215 | ### --------------------------------------------------------------- 216 | 217 | def get_config( rqst = nil ) 218 | scope = "show configuration" 219 | scope.concat( " " + rqst ) if rqst 220 | begin 221 | @ndev.rpc.command( scope, :format => 'text' ).xpath('configuration-output').text 222 | rescue NoMethodError 223 | # indicates no configuration found 224 | nil 225 | rescue => e 226 | # indicates error in request 227 | err = e.rsp.xpath('rpc-error')[0] 228 | err_info = err.xpath('error-info/bad-element').text 229 | err_msg = err.xpath('error-message').text 230 | "ERROR! " + err_msg + ": " + err_info 231 | end 232 | end 233 | 234 | end # class Provider 235 | 236 | 237 | -------------------------------------------------------------------------------- /lib/junos-ez/utils/fs.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | --------------------------------------------------------------------- 3 | FS::Utils is a collection of filesystem utility routines. These do 4 | not map to configuration resources. However, the provider framework 5 | lends itself well in case we need to do something later, yo! 6 | 7 | Each of the FS::Util methods will provide back a rubyized structure 8 | by default. The 'options' hash to each method will allow you to 9 | change the return result in either :text or Junos :xml as well. 10 | 11 | The following is a quick list of the filesystem utility methods, 12 | these following the unix naming counterparts (mostly) 13 | 14 | cat - returns the contents of the file (String) 15 | checksum - returns the checksum of a file 16 | cleanup? - returns Hash info of files that would be removed by ... 17 | cleanup! - performs a system storage cleanup 18 | cp! - local file copy (use 'scp' if you want to copy remote/local) 19 | cwd - change the working directory (String) 20 | df - shows the system storage (Hash) 21 | ls - returns a filesystem listing (Hash) 22 | mv! - rename/move files (true | String error) 23 | pwd - return the current working directory (String) 24 | rm! - remove files (String) 25 | 26 | --------------------------------------------------------------------- 27 | =end 28 | 29 | module Junos::Ez::FS 30 | def self.Utils( ndev, varsym ) 31 | newbie = Junos::Ez::FS::Provider.new( ndev ) 32 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 33 | end 34 | end 35 | 36 | ### ----------------------------------------------------------------- 37 | ### PUBLIC METHODS 38 | ### ----------------------------------------------------------------- 39 | ### class containing filesystem public utility functions 40 | ### these are not in alphabetical order, but I should do that, yo! 41 | ### ----------------------------------------------------------------- 42 | 43 | class Junos::Ez::FS::Provider < Junos::Ez::Provider::Parent 44 | 45 | ### ------------------------------------------------------------- 46 | ### cwd - change the current working directory. This method will 47 | ### return the String of the new working directory or raise 48 | ### and IOError exception if the directory is invalid 49 | ### ------------------------------------------------------------- 50 | 51 | def cwd( directory ) 52 | begin 53 | got = @ndev.rpc.set_cli_working_directory( :directory => directory ) 54 | rescue => e 55 | raise IOError, "invalid directory: #{directory}" 56 | else 57 | got.xpath('working-directory').text 58 | end 59 | end 60 | 61 | ### ------------------------------------------------------------- 62 | ### pwd - retrieve current working directory, return String 63 | ### ------------------------------------------------------------- 64 | 65 | def pwd 66 | ndev.rpc.command("show cli directory").text.strip 67 | end 68 | 69 | def checksum( method, path ) 70 | got = case method 71 | when :md5 72 | @ndev.rpc.get_checksum_information( :path => path ) 73 | when :sha256 74 | @ndev.rpc.get_sha256_checksum_information( :path => path ) 75 | when :sha1 76 | @ndev.rpc.get_sha1_checksum_information( :path => path ) 77 | end 78 | 79 | f_chk = got.xpath('file-checksum') 80 | if (err = f_chk.xpath('rpc-error/error-message')[0]) 81 | raise IOError, err.text.strip 82 | end 83 | f_chk.xpath('checksum').text.strip 84 | end 85 | 86 | ### ------------------------------------------------------------- 87 | ## ls - provides directory listing of files/subdirs. if 88 | ## directory is nil, then the current working directory 89 | ## is used. The following options (opts) are supported 90 | ## 91 | ## :format => [:text, :xml, :hash], default = :hash 92 | ## :recurse => true - recursive listing thru subdirs 93 | ## :detail => true - file details, on if :recurse 94 | ### ------------------------------------------------------------- 95 | 96 | def ls( *args ) 97 | 98 | directory = nil 99 | opts = {} 100 | 101 | case args.count 102 | when 1 103 | if args[0].kind_of? Hash 104 | opts = args[0] 105 | else 106 | directory = args[0] 107 | end 108 | when 2 109 | directory = args[0] 110 | opts = args[1] 111 | end 112 | 113 | # args are the RPC arguments ... 114 | args = {} 115 | args[:path] = directory if directory 116 | args[:recursive] = true if opts[:recurse] 117 | args[:detail] = true if opts[:detail] 118 | args.delete(:detail) if( args[:detail] and args[:recursive]) 119 | 120 | # RPC output format, default is XML 121 | outf = { :format => 'text' } if opts[:format] == :text 122 | 123 | got = @ndev.rpc.file_list( args, outf ) 124 | return nil unless got 125 | 126 | return got.text if opts[:format] == :text 127 | return got if opts[:format] == :xml 128 | 129 | # if we're here, then we need to conver the output 130 | # to a Hash. Joy! 131 | 132 | collect_detail = args[:detail] || args[:recursive] 133 | 134 | ls_hash = {} 135 | got.xpath('directory').each do |dir| 136 | 137 | dir_name = dir.xpath('directory-name').text.strip 138 | dir_hash = {} 139 | 140 | dir_hash[:fileblocks] = dir.xpath('total-file-blocks').text.to_i 141 | files_info = dir.xpath('file-information') 142 | 143 | dir_hash[:files] = {} 144 | dir_hash[:dirs] = {} # sub-directories 145 | 146 | files_info.each do |file| 147 | f_name = file.xpath('file-name').text.strip 148 | f_h = {} 149 | 150 | if file.xpath('file-directory')[0] 151 | dir_hash[:dirs][f_name] = f_h 152 | else 153 | dir_hash[:files][f_name] = f_h 154 | end 155 | 156 | next unless collect_detail 157 | 158 | f_h[:owner] = file.xpath('file-owner').text.strip 159 | f_h[:group] = file.xpath('file-group').text.strip 160 | f_h[:links] = file.xpath('file-links').text.to_i 161 | f_h[:size] = file.xpath('file-size').text.to_i 162 | 163 | xml_when_item(file.xpath('file-symlink-target')) { |i| 164 | f_h[:symlink] = i.text.strip 165 | } 166 | 167 | fp = file.xpath('file-permissions')[0] 168 | f_h[:permissions_text] = fp.attribute('format').value 169 | f_h[:permissions] = fp.text.to_i 170 | 171 | fd = file.xpath('file-date')[0] 172 | f_h[:date] = fd.attribute('format').value 173 | f_h[:date_epoc] = fd.text.to_i 174 | 175 | end # each directory file 176 | ls_hash[ dir_name ] = dir_hash 177 | end # each directory 178 | 179 | return nil if ls_hash.empty? 180 | ls_hash 181 | end # method: ls 182 | 183 | 184 | ### ------------------------------------------------------------- 185 | ### cat - is used to obtain the text contents of the file 186 | ### ------------------------------------------------------------- 187 | 188 | def cat( filename ) 189 | begin 190 | @ndev.rpc.file_show( :filename => filename ).text 191 | rescue => e 192 | raise IOError, e.rsp.xpath('rpc-error/error-message').text.strip 193 | end 194 | end 195 | 196 | 197 | 198 | ### ------------------------------------------------------------- 199 | ### df - shows the system storage information 200 | ### 201 | ### opts[:format] = [:text, :xml, :hash] 202 | ### defaults :hash 203 | ### 204 | ### opts[:size_div] = value to device size values, 205 | ### valid only for :format == :hash 206 | ### ------------------------------------------------------------- 207 | 208 | def df( opts = {} ) 209 | 210 | outf = {:format => 'text' } if opts[:format] == :text 211 | args = { :detail => true } if opts[:size_div] 212 | 213 | got = @ndev.rpc.get_system_storage( args, outf ) 214 | 215 | return got.text if opts[:format] == :text 216 | return got if opts[:format] == :xml 217 | 218 | df_h = {} 219 | ### need to turn this into a Hash 220 | got.xpath('filesystem').each do |fs| 221 | fs_name = fs.xpath('filesystem-name').text.strip 222 | fs_h = {} 223 | df_h[fs_name] = fs_h 224 | 225 | fs_h[:mounted_on] = fs.xpath('mounted-on').text.strip 226 | datum = fs.xpath('total-blocks') 227 | fs_h[:total_blocks] = datum.text.to_i 228 | fs_h[:total_size] = datum.attribute('format').value 229 | 230 | datum = fs.xpath('used-blocks') 231 | fs_h[:used_blocks] = datum.text.to_i 232 | fs_h[:used_size] = datum.attribute('format').value 233 | fs_h[:used_percent] = fs.xpath('used-percent').text.to_i 234 | 235 | datum = fs.xpath('available-blocks') 236 | fs_h[:avail_blocks] = datum.text.to_i 237 | fs_h[:avail_size] = datum.attribute('format').value 238 | if opts[:size_div] 239 | fs_h[:total_size] = fs_h[:total_size].to_i / opts[:size_div] 240 | fs_h[:used_size] = fs_h[:used_size].to_i / opts[:size_div] 241 | fs_h[:avail_size] = fs_h[:avail_size].to_i / opts[:size_div] 242 | end 243 | end 244 | df_h 245 | end 246 | 247 | ### ------------------------------------------------------------- 248 | ### cleanup! will perform the 'request system storage cleanup' 249 | ### command and remove the files. If you want to check which 250 | ### files will be removed, use the cleanup? method first 251 | ### ------------------------------------------------------------- 252 | 253 | def cleanup! 254 | got = @ndev.rpc.request_system_storage_cleanup 255 | gone_h = {} 256 | got.xpath('file-list/file').each do |file| 257 | _cleanup_file_to_h( file, gone_h ) 258 | end 259 | gone_h 260 | end 261 | 262 | ### ------------------------------------------------------------- 263 | ### 'cleanup?' will return information on files that would be 264 | ### removed if cleanup! was executed 265 | ### ------------------------------------------------------------- 266 | 267 | def cleanup? 268 | got = @ndev.rpc.request_system_storage_cleanup( :dry_run => true ) 269 | dryrun_h = {} 270 | got.xpath('file-list/file').each do |file| 271 | _cleanup_file_to_h( file, dryrun_h ) 272 | end 273 | dryrun_h 274 | end 275 | 276 | ### ------------------------------------------------------------- 277 | ### cp! - copies a file. The from_file and to_file can be 278 | ### URL parameters, yo! 279 | ### 280 | ### opts[:source_address] will set the source address of the 281 | ### copy command, useful when URL contain SCP, HTTP 282 | ### ------------------------------------------------------------- 283 | 284 | def cp!( from_file, to_file, opts = {} ) 285 | args = { :source => from_file, :destination => to_file } 286 | args[:source_address] = opts[:source_address] if opts[:source_address] 287 | 288 | begin 289 | got = @ndev.rpc.file_copy( args ) 290 | rescue => e 291 | raise IOError, e.rsp.xpath('rpc-error/error-message').text.strip 292 | else 293 | return true 294 | end 295 | end 296 | 297 | ### ------------------------------------------------------------- 298 | ### 'mv' - just like unix, moves/renames a file 299 | ### ------------------------------------------------------------- 300 | 301 | def mv!( from_path, to_path ) 302 | got = @ndev.rpc.command( "file rename #{from_path} #{to_path}" ) 303 | return true if got.nil? # got no error 304 | raise IOError, got.text 305 | end 306 | 307 | ### ------------------------------------------------------------- 308 | ### rm! - just like unix, removes files 309 | ### ------------------------------------------------------------- 310 | 311 | def rm!( path ) 312 | got = @ndev.rpc.file_delete( :path => path ) 313 | return true if got.nil? # got no error 314 | # otherwise, there was an error, check output 315 | raise IOError, got.text 316 | end 317 | 318 | 319 | end # class Provider 320 | 321 | ### ----------------------------------------------------------------- 322 | ### PRIVATE METHODS 323 | ### ----------------------------------------------------------------- 324 | ### These are helper/private methods, or methods that are current 325 | ### work-in-progress/under-investigation 326 | ### ----------------------------------------------------------------- 327 | 328 | class Junos::Ez::FS::Provider 329 | private 330 | 331 | ### private method used to convert 'cleanup' file XML 332 | ### to hash structure and bind it to collecting hash 333 | 334 | def _cleanup_file_to_h( file, attach_h ) 335 | file_name = file.xpath('file-name').text.strip 336 | file_h = {} 337 | data = file.xpath('size') 338 | file_h[:size_text] = data.attribute('format').value 339 | file_h[:size] = data.text.to_i 340 | file_h[:date] = file.xpath('date').text.strip 341 | attach_h[file_name] = file_h 342 | file_h 343 | end 344 | 345 | ##### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 346 | ##### ... HERE THERE BE MONSTERS .... 347 | ##### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 348 | 349 | ### ------------------------------------------------------------- 350 | ### 'diff' just like unix; patch output 351 | ### @@@ something is getting messed up in the XML translation 352 | ### @@@ as control characters like (<,>) are getting munged. 353 | ### @@@ need to investigate with Nokogiri .... 354 | ### ------------------------------------------------------------- 355 | 356 | def diff___( from_file, to_file ) 357 | raise StandardError, "Under investigation" 358 | got = @ndev.rpc.file_compare( :from_file => from_file, :to_file => to_file ) 359 | end 360 | 361 | # create a .tar file from the files in the given directory. 362 | # the filename does not need to include the .tar extension, but 363 | # if you include it, that's ok. 364 | # NOTE: cannot specify an arbitrary list of files, per Junos RPC 365 | 366 | ### !!!! these are only allowed from the CLI, at least as tested 367 | ### !!!! on an vSRX. Need to check on other platforms, etc. 368 | 369 | def tar___( directory, filename ) 370 | raise StandardError, "Under investigation" 371 | got = @ndev.rpc.file_archive( :destination => filename, :source => directory ) 372 | end 373 | 374 | # create a .tgz file from the files in the given directory. 375 | # the filename does not need to include the .tgz extension, but 376 | # if you include it, that's ok. 377 | # NOTE: cannot specify an arbitrary list of files, per Junos RPC 378 | 379 | def tgz___( directory, filename ) 380 | raise StandardError, "Under investigation" 381 | got = @ndev.rpc.file_archive( :destination => filename, :source => directory, :compress => true ) 382 | end 383 | 384 | end 385 | 386 | -------------------------------------------------------------------------------- /lib/junos-ez/version.rb: -------------------------------------------------------------------------------- 1 | module Junos; end 2 | module Junos::Ez; end 3 | 4 | module Junos::Ez 5 | VERSION = '1.0.3'.freeze 6 | end 7 | -------------------------------------------------------------------------------- /lib/junos-ez/vlans.rb: -------------------------------------------------------------------------------- 1 | require "junos-ez/provider" 2 | 3 | module Junos::Ez::Vlans 4 | 5 | PROPERTIES = [ 6 | :vlan_id, # Fixnum, [ 1 .. 4094 ] 7 | :description, # String, description 8 | :no_mac_learning, # [ true | nil ] - used to disable MAC-address learning 9 | :interfaces, # READ-ONLY, array of bound interface names 10 | ] 11 | 12 | def self.Provider( ndev, varsym ) 13 | newbie = case ndev.fact :switch_style 14 | when :VLAN 15 | Junos::Ez::Vlans::Provider::VLAN.new( ndev ) 16 | when :VLAN_L2NG 17 | Junos::Ez::Vlans::Provider::VLAN_L2NG.new( ndev ) 18 | when :BRIDGE_DOMAIN 19 | Junos::Ez::Vlans::Provider::BRIDGE_DOMAIN.new( ndev ) 20 | else 21 | raise Junos::Ez::NoProviderError, "target does not support vlan bridges" 22 | end 23 | newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES 24 | Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie ) 25 | end 26 | 27 | class Provider < Junos::Ez::Provider::Parent 28 | # common parenting goes here ... 29 | 30 | end 31 | 32 | end 33 | 34 | require 'junos-ez/vlans/vlan' 35 | require 'junos-ez/vlans/vlan_l2ng' 36 | require 'junos-ez/vlans/bridge_domain' 37 | 38 | 39 | -------------------------------------------------------------------------------- /lib/junos-ez/vlans/bridge_domain.rb: -------------------------------------------------------------------------------- 1 | class Junos::Ez::Vlans::Provider::BRIDGE_DOMAIN < Junos::Ez::Vlans::Provider 2 | 3 | ### --------------------------------------------------------------- 4 | ### XML top placement 5 | ### --------------------------------------------------------------- 6 | 7 | def xml_at_top 8 | Nokogiri::XML::Builder.new{|x| x.configuration{ 9 | x.send( :'bridge-domains' ) { x.domain { x.name @name 10 | return x 11 | }} 12 | }} 13 | end 14 | 15 | ### --------------------------------------------------------------- 16 | ### XML readers 17 | ### --------------------------------------------------------------- 18 | 19 | def xml_read! 20 | cfg_xml = @ndev.rpc.get_configuration( xml_at_top ) 21 | return nil unless (@has_xml = cfg_xml.xpath('//domain')[0]) 22 | xml_read_parser( @has_xml, @has ) 23 | end 24 | 25 | def xml_get_has_xml( xml ) 26 | xml.xpath('//domain')[0] 27 | end 28 | 29 | def xml_read_parser( as_xml, as_hash ) 30 | set_has_status( as_xml, as_hash ) 31 | as_hash[:vlan_id] = as_xml.xpath('vlan-id').text.to_i 32 | as_hash[:description] = as_xml.xpath('description').text 33 | as_hash[:no_mac_learning] = as_xml.xpath('bridge-options/no-mac-learning').empty? ? :disable : :enable 34 | return true 35 | end 36 | 37 | ### --------------------------------------------------------------- 38 | ### XML writers 39 | ### --------------------------------------------------------------- 40 | 41 | def xml_on_create( xml ) 42 | xml.send( :'domain-type', 'bridge' ) 43 | end 44 | 45 | def xml_change_no_mac_learning( xml ) 46 | no_ml = @should[:no_mac_learning] 47 | return unless ( exists? and no_ml ) 48 | xml.send(:'bridge-options') { 49 | xml.send(:'no-mac-learning', (no_ml == :enable) ? nil : Netconf::JunosConfig::DELETE ) 50 | } 51 | end 52 | 53 | def xml_change_vlan_id( xml ) 54 | xml.send( :'vlan-id', @should[:vlan_id] ) 55 | end 56 | 57 | def xml_change_description( xml ) 58 | xml.description @should[:description] 59 | end 60 | 61 | end 62 | 63 | 64 | ##### --------------------------------------------------------------- 65 | ##### Provider collection methods 66 | ##### --------------------------------------------------------------- 67 | 68 | class Junos::Ez::Vlans::Provider::BRIDGE_DOMAIN 69 | 70 | def build_list 71 | bd_cfgs = @ndev.rpc.get_configuration{ |x| x.send :'bridge-domains' } 72 | bd_cfgs.xpath('bridge-domains/domain').collect do |domain| 73 | domain.xpath('name').text 74 | end 75 | end 76 | 77 | def build_catalog 78 | @catalog = {} 79 | bd_cfgs = @ndev.rpc.get_configuration{ |x| x.send :'bridge-domains' } 80 | bd_cfgs.xpath('bridge-domains/domain').collect do |domain| 81 | name = domain.xpath('name').text 82 | @catalog[name] = {} 83 | xml_read_parser( domain, @catalog[name] ) 84 | end 85 | return @catalog 86 | end 87 | 88 | end 89 | 90 | -------------------------------------------------------------------------------- /lib/junos-ez/vlans/vlan.rb: -------------------------------------------------------------------------------- 1 | class Junos::Ez::Vlans::Provider::VLAN < Junos::Ez::Vlans::Provider 2 | 3 | ### --------------------------------------------------------------- 4 | ### XML top placement 5 | ### --------------------------------------------------------------- 6 | 7 | def xml_at_top 8 | Nokogiri::XML::Builder.new{|x| x.configuration{ 9 | x.vlans { x.vlan { x.name @name 10 | return x 11 | }} 12 | }} 13 | end 14 | 15 | ### --------------------------------------------------------------- 16 | ### XML readers 17 | ### --------------------------------------------------------------- 18 | 19 | def xml_get_has_xml( xml ) 20 | xml.xpath('//vlan')[0] 21 | end 22 | 23 | def xml_read_parser( as_xml, as_hash ) 24 | set_has_status( as_xml, as_hash ) 25 | 26 | as_hash[:vlan_id] = as_xml.xpath('vlan-id').text.to_i 27 | xml_when_item(as_xml.xpath('description')){ |i| as_hash[:description] = i.text } 28 | xml_when_item(as_xml.xpath('no-mac-learning')){ as_hash[:no_mac_learning] = :enable } 29 | 30 | # get a brief list of the interfaces on this vlan 31 | got = @ndev.rpc.get_vlan_information( :vlan_name => @name || as_xml.xpath('name').text ) 32 | as_hash[:interfaces] = got.xpath('//vlan-member-interface').collect{|ifs| ifs.text.strip } 33 | as_hash[:interfaces] = nil if as_hash[:interfaces][0] == "None" 34 | 35 | return true 36 | end 37 | 38 | ### --------------------------------------------------------------- 39 | ### XML writers 40 | ### --------------------------------------------------------------- 41 | 42 | def xml_change_no_mac_learning( xml ) 43 | no_ml = @should[:no_mac_learning] 44 | return unless exists? and no_ml 45 | xml.send(:'no-mac-learning', no_ml ? nil : Netconf::JunosConfig::DELETE ) 46 | end 47 | 48 | def xml_change_vlan_id( xml ) 49 | xml.send(:'vlan-id', @should[:vlan_id] ) 50 | end 51 | 52 | def xml_change_description( xml ) 53 | value = @should[:description] 54 | xml.description value ? value : Netconf::JunosConfig::DELETE 55 | end 56 | 57 | end 58 | 59 | ##### --------------------------------------------------------------- 60 | ##### Provider collection methods 61 | ##### --------------------------------------------------------------- 62 | 63 | class Junos::Ez::Vlans::Provider::VLAN 64 | 65 | def build_list 66 | xml_cfgs = @ndev.rpc.get_configuration{ |x| x.send :'vlans' } 67 | xml_cfgs.xpath('vlans/vlan').collect do |vlan| 68 | vlan.xpath('name').text 69 | end 70 | end 71 | 72 | def build_catalog 73 | @catalog = {} 74 | xml_cfgs = @ndev.rpc.get_configuration{ |x| x.send :'vlans' } 75 | xml_cfgs.xpath('vlans/vlan').collect do |vlan| 76 | name = vlan.xpath('name').text 77 | @catalog[name] = {} 78 | xml_read_parser( vlan, @catalog[name] ) 79 | end 80 | return @catalog 81 | end 82 | 83 | end 84 | 85 | ##### --------------------------------------------------------------- 86 | ##### Provider operational methods 87 | ##### --------------------------------------------------------------- 88 | 89 | class Junos::Ez::Vlans::Provider::VLAN 90 | 91 | ### --------------------------------------------------------------- 92 | ### interfaces - returns a Hash of each interface in the VLAN 93 | ### each interface (key) will identify: 94 | ### :mode = [ :access | :trunk ] 95 | ### :native = true if (:mode == :trunk) and this VLAN is the 96 | ### native vlan-id (untagged packets) 97 | ### --------------------------------------------------------------- 98 | 99 | def interfaces( opts = {} ) 100 | raise ArgumentError, "not a resource" if is_provider? 101 | 102 | args = {} 103 | args[:vlan_name] = @name 104 | args[:extensive] = true 105 | got = @ndev.rpc.get_vlan_information( args ) 106 | 107 | members = got.xpath('vlan/vlan-detail/vlan-member-list/vlan-member') 108 | ifs_h = {} 109 | members.each do |port| 110 | port_name = port.xpath('vlan-member-interface').text.split('.')[0] 111 | port_h = {} 112 | port_h[:mode] = port.xpath('vlan-member-port-mode').text.to_sym 113 | native = (port.xpath('vlan-member-tagness').text == 'untagged') 114 | port_h[:native] = true if( native and port_h[:mode] == :trunk) 115 | ifs_h[port_name] = port_h 116 | end 117 | ifs_h 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/junos-ez/vlans/vlan_l2ng.rb: -------------------------------------------------------------------------------- 1 | class Junos::Ez::Vlans::Provider::VLAN_L2NG < Junos::Ez::Vlans::Provider 2 | 3 | ### --------------------------------------------------------------- 4 | ### XML top placement 5 | ### --------------------------------------------------------------- 6 | 7 | def xml_at_top 8 | Nokogiri::XML::Builder.new{|x| x.configuration{ 9 | x.vlans { x.vlan { x.name @name 10 | return x 11 | }} 12 | }} 13 | end 14 | 15 | ### --------------------------------------------------------------- 16 | ### XML readers 17 | ### --------------------------------------------------------------- 18 | 19 | def xml_get_has_xml( xml ) 20 | xml.xpath('//vlan')[0] 21 | end 22 | 23 | def xml_read_parser( as_xml, as_hash ) 24 | set_has_status( as_xml, as_hash ) 25 | 26 | as_hash[:vlan_id] = as_xml.xpath('vlan-id').text.to_i 27 | xml_when_item(as_xml.xpath('description')){ |i| as_hash[:description] = i.text } 28 | 29 | xml_when_item(as_xml.xpath('switch-options/no-mac-learning')) { 30 | as_hash[:no_mac_learning] = :enable 31 | } 32 | 33 | # get a brief list of the interfaces on this vlan 34 | got = @ndev.rpc.get_vlan_information( :vlan_name => @name || as_xml.xpath('name').text ) 35 | as_hash[:interfaces] = got.xpath('//l2ng-l2rtb-vlan-member-interface').collect{|ifs| ifs.text.strip } 36 | as_hash[:interfaces] = nil if as_hash[:interfaces][0] == "None" 37 | 38 | return true 39 | end 40 | 41 | ### --------------------------------------------------------------- 42 | ### XML writers 43 | ### --------------------------------------------------------------- 44 | 45 | def xml_change_no_mac_learning( xml ) 46 | no_ml = @should[:no_mac_learning] 47 | return unless exists? and no_ml 48 | xml.send(:'switch-options') { 49 | xml.send(:'no-mac-learning', no_ml == :enable ? nil : Netconf::JunosConfig::DELETE ) 50 | } 51 | end 52 | 53 | def xml_change_vlan_id( xml ) 54 | xml.send(:'vlan-id', @should[:vlan_id] ) 55 | end 56 | 57 | def xml_change_description( xml ) 58 | value = @should[:description] 59 | xml.description value ? value : Netconf::JunosConfig::DELETE 60 | end 61 | 62 | end 63 | 64 | ##### --------------------------------------------------------------- 65 | ##### Provider collection methods 66 | ##### --------------------------------------------------------------- 67 | 68 | class Junos::Ez::Vlans::Provider::VLAN_L2NG 69 | 70 | def build_list 71 | xml_cfgs = @ndev.rpc.get_configuration{ |x| x.send :'vlans' } 72 | xml_cfgs.xpath('vlans/vlan').collect do |vlan| 73 | vlan.xpath('name').text 74 | end 75 | end 76 | 77 | def build_catalog 78 | @catalog = {} 79 | xml_cfgs = @ndev.rpc.get_configuration{ |x| x.send :'vlans' } 80 | xml_cfgs.xpath('vlans/vlan').collect do |vlan| 81 | name = vlan.xpath('name').text 82 | @catalog[name] = {} 83 | xml_read_parser( vlan, @catalog[name] ) 84 | end 85 | return @catalog 86 | end 87 | 88 | end 89 | 90 | ##### --------------------------------------------------------------- 91 | ##### Provider operational methods 92 | ##### --------------------------------------------------------------- 93 | 94 | class Junos::Ez::Vlans::Provider::VLAN_L2NG 95 | 96 | ### --------------------------------------------------------------- 97 | ### interfaces - returns a Hash of each interface in the VLAN 98 | ### each interface (key) will identify: 99 | ### :mode = [ :access | :trunk ] 100 | ### :native = true if (:mode == :trunk) and this VLAN is the 101 | ### native vlan-id (untagged packets) 102 | ### --------------------------------------------------------------- 103 | 104 | def interfaces( opts = {} ) 105 | raise ArgumentError, "not a resource" if is_provider? 106 | 107 | args = {} 108 | args[:vlan_name] = @name 109 | args[:extensive] = true 110 | got = @ndev.rpc.get_vlan_information( args ) 111 | 112 | member_pfx = 'l2ng-l2rtb-vlan-member' 113 | members = got.xpath("//#{member_pfx}-interface") 114 | ifs_h = {} 115 | members.each do |port| 116 | port_name = port.text.split('.')[0] 117 | port_h = {} 118 | port_h[:mode] = port.xpath("following-sibling::#{member_pfx}-interface-mode[1]").text.to_sym 119 | tgd = port.xpath("following-sibling::#{member_pfx}-tagness[1]").text 120 | native = (tgd == 'untagged') 121 | port_h[:native] = true if( native and port_h[:mode] == :trunk) 122 | ifs_h[port_name] = port_h 123 | end 124 | ifs_h 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | --------------------------------------------------------------------------------