diff --git a/CHANGELOG.md b/CHANGELOG.md index 4591feef..9a1e01ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ Changelog * `facility` * Extend interface with attributes: * `ipv6_redirects` +* Added ability to specify environment at run time + +Example: +```ruby +env = { host: '192.168.1.1', port: nil, username: 'admin', password: 'admin123', cookie: nil } +Cisco::Environment.add_env('default', env) +``` ### Changed diff --git a/README.md b/README.md index c441d9eb..8866850d 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,14 @@ An example configuration file (illustrating each of the above scenarios) is prov *For security purposes, it is highly recommended that access to this file be restricted to only the owning user (`chmod 0600`).* +Configuration may also be specified at runtime and can be used in the absence of configuration files or to override them. + +Example: +```ruby +env = { host: '192.168.1.1', port: nil, username: 'admin', password: 'admin123', cookie: nil } +Cisco::Environment.add_env('default', env) +``` + ## Documentation ### Client @@ -151,12 +159,18 @@ client2 = Cisco::Client.create('n9k') # Warning: Make sure to exclude devices using the 'no_proxy' environment variable # to ensure successful remote connections. +# Add runtime configuration for remote device and connect +env = { host: '192.168.1.1', port: nil, username: 'admin', password: 'admin123', cookie: nil } +Cisco::Environment.add_env('remote', env) +client3 = Cisco::Client.create('remote') + # Use connections to get and set device state. client1.set(values: 'feature vtp') client1.set(values: 'vtp domain mycompany.com') puts client1.get(command: 'show vtp status | inc Domain') puts client2.get(command: 'show version') +puts client3.get(command: 'show version') ``` #### High-level Node API diff --git a/ext/mkrf_conf.rb b/ext/mkrf_conf.rb index 940c99d3..6abfcfab 100644 --- a/ext/mkrf_conf.rb +++ b/ext/mkrf_conf.rb @@ -37,7 +37,7 @@ os == 'ios_xr' || deps << Gem::Dependency.new('net_http_unix', '~> 0.2', '>= 0.2.1') # NX-OS doesn't need gRPC - os == 'nexus' || deps << Gem::Dependency.new('grpc', '~> 0.12') + os == 'nexus' || deps << Gem::Dependency.new('grpc', '~> 1.14.1') deps.each do |dep| installed = dep.matching_specs diff --git a/lib/cisco_node_utils/environment.rb b/lib/cisco_node_utils/environment.rb index e3b84df3..7d0ad910 100644 --- a/lib/cisco_node_utils/environment.rb +++ b/lib/cisco_node_utils/environment.rb @@ -1,6 +1,7 @@ +# August 2018 # March 2016, Glenn F. Matthews # -# Copyright (c) 2016 Cisco and/or its affiliates. +# Copyright (c) 2016-2018 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -90,6 +91,12 @@ def self.data_from_file(path) {} end + def self.add_env(env_name, env_hash) + fail ArgumentError, 'empty environment name' if env_name.empty? + fail TypeError, 'invalid environment hash' unless env_hash.is_a?(Hash) + @environments[env_name] = env_hash + end + def self.strings_to_symbols(hash) Hash[hash.map { |k, v| [k.to_sym, v] }] end diff --git a/spec/environment_spec.rb b/spec/environment_spec.rb index 8ed8fd30..1b0ffc0f 100644 --- a/spec/environment_spec.rb +++ b/spec/environment_spec.rb @@ -70,6 +70,37 @@ class << Cisco::Environment end end + describe '.add_env' do + it 'rejects empty environment name' do + expect { described_class.add_env('', {}) }.to \ + raise_error(ArgumentError, 'empty environment name') + end + + it 'rejects incorrectly typed environment hash' do + expect { described_class.add_env('default', 'hash') }.to \ + raise_error(TypeError, 'invalid environment hash') + end + + context 'environment can be loaded by funcion' do + expected = { + host: '192.168.1.1', + port: nil, + username: 'admin', + password: 'admin', + cookie: nil, + } + it 'can be loaded explicitly by name' do + described_class.add_env('test', expected) + expect(Cisco::Environment.environment('test')).to eq(expected) + end + + it 'can be default' do + described_class.add_env('default', expected) + expect(Cisco::Environment.environment).to eq(expected) + end + end + end + describe '.environments' do before(:each) do allow(Cisco::Environment).to receive(:data_from_file).and_return({}) @@ -162,6 +193,82 @@ class << Cisco::Environment }, ) end + + it 'loaded file can be overridden by add_env' do + expect(Cisco::Environment).to receive(:data_from_file).with( + '/etc/cisco_node_utils.yaml').and_return(global_config) + expect(Cisco::Environment).to receive(:data_from_file).with( + '~/cisco_node_utils.yaml').and_return(user_config) + expect(Cisco::Environment.environments).to eq( + 'default' => { + host: '127.0.0.1', # global config + port: 57_799, # user overrides global + username: 'user', # user config + password: nil, # auto-populated with nil + cookie: nil, + }, + 'global' => { # global config + host: nil, + port: nil, + username: 'global', + password: 'global', + cookie: nil, + }, + 'user' => { # user config + host: nil, + port: nil, + username: 'user', + password: 'user', + cookie: nil, + }, + ) + added_env = { + host: '192.168.1.1', + port: nil, + username: 'admin', + password: 'admin', + cookie: nil, + } + overide_defult = { + host: '192.168.2.2', + port: nil, + username: 'overridden', + password: 'overridden', + cookie: nil, + } + described_class.add_env('added', added_env) + described_class.add_env('default', overide_defult) + expect(Cisco::Environment.environments).to eq( + 'added' => { # added by method + host: '192.168.1.1', + port: nil, + username: 'admin', + password: 'admin', + cookie: nil, + }, + 'default' => { + host: '192.168.2.2', # method overrides files + port: nil, # method overrides files + username: 'overridden', # method overrides files + password: 'overridden', # method overrides files + cookie: nil, # auto-popuplated with nil + }, + 'global' => { # global config + host: nil, + port: nil, + username: 'global', + password: 'global', + cookie: nil, + }, + 'user' => { # user config + host: nil, + port: nil, + username: 'user', + password: 'user', + cookie: nil, + }, + ) + end end context '.environment' do