├── .ruby-version ├── .rspec ├── Rakefile ├── lib ├── infoblox │ ├── version.rb │ ├── resource │ │ ├── txt.rb │ │ ├── mx.rb │ │ ├── zone_forward.rb │ │ ├── srv.rb │ │ ├── network_container.rb │ │ ├── zone_auth.rb │ │ ├── cname.rb │ │ ├── lease.rb │ │ ├── arecord.rb │ │ ├── search.rb │ │ ├── roaminghost.rb │ │ ├── host_ipv4addr.rb │ │ ├── aaaa_record.rb │ │ ├── fixedaddress.rb │ │ ├── ptr.rb │ │ ├── range.rb │ │ ├── network.rb │ │ ├── ipv4address.rb │ │ ├── host.rb │ │ └── grid.rb │ ├── connection.rb │ └── resource.rb └── infoblox.rb ├── Gemfile ├── .gitignore ├── spec ├── integration │ ├── ptr_spec.rb │ ├── cname_spec.rb │ ├── arecord_spec.rb │ ├── host_ipv4addr_spec.rb │ ├── connection_timeout_spec.rb │ ├── host_spec.rb │ └── zone_auth_spec.rb ├── host_ipv4addr_spec.rb ├── range_spec.rb ├── connection_spec.rb ├── host_spec.rb ├── search_spec.rb ├── spec_helper.rb └── resource_spec.rb ├── .travis.yml ├── LICENSE.txt ├── infoblox.gemspec ├── RELEASES.md └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.3 -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /lib/infoblox/version.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | VERSION = "3.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in infoblox.gemspec 4 | gemspec 5 | 6 | if ENV['INTEGRATION'] 7 | group :test do 8 | gem 'highline' 9 | gem 'pry' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | config.yml 19 | -------------------------------------------------------------------------------- /lib/infoblox/resource/txt.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Txt < Resource 3 | remote_attr_accessor :extattrs, 4 | :extensible_attributes, 5 | :name, 6 | :text, 7 | :ttl, 8 | :view 9 | 10 | wapi_object "record:txt" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/integration/ptr_spec.rb: -------------------------------------------------------------------------------- 1 | if ENV['INTEGRATION'] 2 | require 'spec_helper' 3 | 4 | describe 'Infoblox::Ptr' do 5 | describe '.find' do 6 | it 'should work' do 7 | each_version do 8 | expect(Infoblox::Ptr.find(connection, "ipv4addr~" => "10.*", "_max_results" => 1)).to_not be_nil 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/infoblox/resource/mx.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Mx < Resource 3 | remote_attr_accessor :extattrs, 4 | :extensible_attributes, 5 | :mail_exchanger, 6 | :name, 7 | :preference, 8 | :view 9 | 10 | wapi_object "record:mx" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/infoblox/resource/zone_forward.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class ZoneForward < Infoblox::Resource 3 | remote_attr_accessor :comment, 4 | :disable, 5 | :forward_to, 6 | :view 7 | 8 | remote_post_accessor :fqdn 9 | 10 | remote_attr_reader :parent 11 | 12 | wapi_object 'zone_forward' 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/integration/cname_spec.rb: -------------------------------------------------------------------------------- 1 | if ENV['INTEGRATION'] 2 | require 'spec_helper' 3 | 4 | describe 'Infoblox::Cname' do 5 | describe '.find' do 6 | it 'should work' do 7 | each_version do 8 | # the empty result will be [], so nil is bad. 9 | expect(Infoblox::Cname.find(connection, :_max_results => 1)).to_not be_nil 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/integration/arecord_spec.rb: -------------------------------------------------------------------------------- 1 | if ENV['INTEGRATION'] 2 | require 'spec_helper' 3 | 4 | describe 'Infoblox::Arecord' do 5 | describe '.find' do 6 | it 'should work' do 7 | each_version do 8 | # the empty result will be [], so nil is bad. 9 | expect(Infoblox::Arecord.find(connection, :_max_results => 1)).to_not be_nil 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/integration/host_ipv4addr_spec.rb: -------------------------------------------------------------------------------- 1 | if ENV['INTEGRATION'] 2 | require 'spec_helper' 3 | 4 | describe 'Infoblox::HostIpv4addr' do 5 | describe '.find' do 6 | it 'should work' do 7 | each_version do 8 | # the empty result will be [], so nil is bad. 9 | expect(Infoblox::HostIpv4addr.find(connection, :_max_results => 1)).to_not be_nil 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/infoblox/resource/srv.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Srv < Resource 3 | remote_attr_accessor :extattrs, 4 | :extensible_attributes, 5 | :name, 6 | :port, 7 | :priority, 8 | :target, 9 | :view, 10 | :weight 11 | 12 | wapi_object "record:srv" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/infoblox/resource/network_container.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class NetworkContainer < Resource 3 | remote_attr_accessor :extattrs, 4 | :extensible_attributes, 5 | :network, 6 | :network_view 7 | 8 | remote_post_accessor :auto_create_reversezone 9 | 10 | remote_attr_reader :network_container 11 | 12 | wapi_object "networkcontainer" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/infoblox/resource/zone_auth.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # ZoneAuth is only availabe in WAPI_VERSION >= 1.2 3 | # 4 | module Infoblox 5 | class ZoneAuth < Resource 6 | remote_attr_accessor :comment, 7 | :disable, 8 | :extattrs 9 | 10 | remote_post_accessor :fqdn, 11 | :view 12 | 13 | remote_attr_reader :address, 14 | :network_view 15 | 16 | wapi_object "zone_auth" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/infoblox/resource/cname.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Cname < Resource 3 | remote_attr_accessor :canonical, 4 | :comment, 5 | :disable, 6 | :extattrs, 7 | :extensible_attributes, 8 | :name, 9 | :ttl, 10 | :view 11 | 12 | remote_attr_reader :zone 13 | 14 | 15 | wapi_object "record:cname" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/infoblox/resource/lease.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | # NB: Only exists on WAPI 1.1 and up 3 | class Lease < Resource 4 | remote_attr_reader :address, 5 | :binding_state, 6 | :client_hostname, 7 | :ends, 8 | :hardware, 9 | :network, 10 | :network_view, 11 | :protocol, 12 | :starts 13 | 14 | wapi_object 'lease' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.6.3 4 | - 2.5.5 5 | - 2.4.6 6 | - 2.3.8 7 | - 2.2.10 8 | - 2.1.10 9 | - 1.9.3 10 | - jruby-9.2.7.0 11 | # https://docs.travis-ci.com/user/languages/ruby/ 12 | # https://bundler.io/blog/2019/01/04/an-update-on-the-bundler-2-release.html 13 | before_install: 14 | - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true 15 | - gem uninstall bundler -v '>= 2' || true 16 | - gem install bundler -v '< 2' 17 | - gem list | grep bundler 18 | script: bundle exec rspec 19 | -------------------------------------------------------------------------------- /lib/infoblox/resource/arecord.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | # Maps FQDN to IPAddress 3 | class Arecord < Resource 4 | remote_attr_accessor :comment, 5 | :disable, 6 | :extattrs, 7 | :extensible_attributes, 8 | :ipv4addr, 9 | :name, 10 | :ttl 11 | 12 | # view can not be updated 13 | remote_post_accessor :view 14 | 15 | remote_attr_reader :zone 16 | 17 | wapi_object 'record:a' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/host_ipv4addr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe Infoblox::HostIpv4addr do 3 | 4 | it "should have correct post attributes" do 5 | expected = [:host] 6 | expect(Infoblox::HostIpv4addr.remote_post_attrs).to eq(expected) 7 | 8 | expected = [ 9 | :bootfile, 10 | :configure_for_dhcp, 11 | :ipv4addr, 12 | :mac, 13 | :network, 14 | :nextserver, 15 | :use_bootfile, 16 | :use_nextserver].map(&:to_s).sort 17 | expect(Infoblox::HostIpv4addr.remote_attrs.map(&:to_s).sort).to eq(expected) 18 | end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /lib/infoblox/resource/search.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Search < Resource 3 | wapi_object "search" 4 | 5 | # does not return objects not implemented in this library 6 | def self.find(connection, params) 7 | JSON.parse(connection.get(resource_uri,params).body).map do |jobj| 8 | klass = resource_map[jobj["_ref"].split("/").first] 9 | if klass.nil? 10 | puts jobj['_ref'] 11 | warn "umapped resource: #{jobj["_ref"]}" 12 | else 13 | klass.new(jobj.merge({:connection => connection})) 14 | end 15 | end.compact 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/infoblox/resource/roaminghost.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | # minimum WAPI version: 1.1 3 | class RoamingHost < Resource 4 | remote_attr_accessor :extattrs, 5 | :mac, 6 | :bootfile, 7 | :use_bootfile, 8 | :nextserver, 9 | :use_nextserver, 10 | :name, 11 | :network_view, 12 | :options, 13 | :match_client, 14 | :comment 15 | 16 | wapi_object 'roaminghost' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/infoblox/resource/host_ipv4addr.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class HostIpv4addr < Resource 3 | remote_attr_accessor :configure_for_dhcp, 4 | :ipv4addr, 5 | :mac, 6 | :network, 7 | :nextserver, 8 | :use_nextserver, 9 | :bootfile, 10 | :use_bootfile 11 | 12 | remote_post_accessor :host 13 | 14 | wapi_object "record:host_ipv4addr" 15 | 16 | def delete 17 | raise "Not supported" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/infoblox/resource/aaaa_record.rb: -------------------------------------------------------------------------------- 1 | # An AAAA (address) record maps a domain name to an IPv6 address. 2 | module Infoblox 3 | class AAAArecord < Resource 4 | remote_attr_accessor :comment, 5 | :disable, 6 | :extattrs, 7 | :extensible_attributes, 8 | :ipv6addr, 9 | :name, 10 | :ttl, 11 | :use_ttl, 12 | :view 13 | 14 | remote_attr_reader :dns_name, 15 | :zone 16 | 17 | wapi_object "record:aaaa" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/infoblox/resource/fixedaddress.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | # minimum WAPI version: 1.1 3 | class Fixedaddress < Resource 4 | remote_attr_accessor :extattrs, 5 | :ipv4addr, 6 | :mac, 7 | :name, 8 | :network, 9 | :network_view, 10 | :options, 11 | :match_client, 12 | :bootfile, 13 | :use_bootfile, 14 | :nextserver, 15 | :use_nextserver, 16 | :comment 17 | 18 | wapi_object "fixedaddress" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/infoblox/resource/ptr.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Ptr < Resource 3 | remote_attr_accessor :comment, 4 | :disable, 5 | :ipv4addr, 6 | :ipv6addr, 7 | :name, 8 | :ptrdname, 9 | :ttl, 10 | :extattrs, 11 | :extensible_attributes, 12 | :view 13 | 14 | def self._return_fields 15 | if Infoblox.wapi_version == '1.0' 16 | super.split(",").find_all{|f| f != 'ipv6addr'}.join(",") 17 | else 18 | super 19 | end 20 | end 21 | 22 | remote_attr_reader :zone 23 | 24 | wapi_object "record:ptr" 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/infoblox.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'json' 3 | require 'faraday' 4 | require 'faraday_middleware' 5 | require 'openssl' 6 | require 'infoblox/version' 7 | require 'infoblox/connection' 8 | require 'infoblox/resource' 9 | 10 | module Infoblox 11 | DEBUG = ENV['DEBUG'] 12 | 13 | def wapi_version 14 | @wapi_version ||= (ENV['WAPI_VERSION'] || '2.0') 15 | end 16 | module_function :wapi_version 17 | 18 | def wapi_version=(v) 19 | @wapi_version = v 20 | end 21 | module_function :wapi_version= 22 | 23 | def base_path 24 | '/wapi/v' + Infoblox.wapi_version + '/' 25 | end 26 | module_function :base_path 27 | end 28 | 29 | # Require everything in the resource directory 30 | Dir[File.expand_path('../infoblox/resource/*.rb', __FILE__)].each do |f| 31 | require f 32 | end 33 | -------------------------------------------------------------------------------- /lib/infoblox/resource/range.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Range < Resource 3 | remote_attr_accessor :end_addr, 4 | :extattrs, 5 | :extensible_attributes, 6 | :network_view, 7 | :start_addr 8 | 9 | wapi_object "range" 10 | 11 | ## 12 | # Invoke the same-named function on the range resource in WAPI, 13 | # returning an array of available IP addresses. 14 | # You may optionally specify how many IPs you want (num) and which ones to 15 | # exclude from consideration (array of IPv4 address strings). 16 | # 17 | def next_available_ip(num=1, exclude=[]) 18 | post_body = { 19 | :num => num.to_i, 20 | :exclude => exclude 21 | } 22 | JSON.parse(connection.post(resource_uri + "?_function=next_available_ip", post_body).body)["ips"] 23 | end 24 | 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/integration/connection_timeout_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'socket' 3 | 4 | describe 'Infoblox::Connection' do 5 | it "should timeout in defined time" do 6 | begin 7 | server = TCPServer.new('localhost', 8813) 8 | host = 'localhost:8813' 9 | conn_params = { 10 | :host => host, 11 | :timeout => 4 12 | } 13 | uri = "/wapi/v1.0" 14 | 15 | ic = Infoblox::Connection.new(conn_params) 16 | start = Time.now 17 | if RUBY_VERSION < "2.0" 18 | # old pinned version of faraday gem throws a different exception 19 | expect { ic.get(uri) }.to raise_error(Faraday::TimeoutError) 20 | else 21 | expect { ic.get(uri) }.to raise_error(Faraday::ConnectionFailed) 22 | end 23 | finish = Time.now 24 | # this should take approx. 4 seconds but let's be safe 25 | expect(finish - start).to be_between(3, 55) 26 | ensure 27 | server.close 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/infoblox/resource/network.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Network < Resource 3 | remote_attr_accessor :comment, 4 | :extattrs, 5 | :extensible_attributes, 6 | :network, 7 | :network_view 8 | 9 | remote_attr_reader :network_container 10 | 11 | remote_post_accessor :auto_create_reversezone 12 | 13 | wapi_object "network" 14 | 15 | ## 16 | # Invoke the same-named function on the network resource in WAPI, 17 | # returning an array of available IP addresses. 18 | # You may optionally specify how many IPs you want (num) and which ones to 19 | # exclude from consideration (array of IPv4 address strings). 20 | # 21 | def next_available_ip(num=1, exclude=[]) 22 | post_body = { 23 | :num => num.to_i, 24 | :exclude => exclude 25 | } 26 | JSON.parse(connection.post(resource_uri + "?_function=next_available_ip", post_body).body)["ips"] 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/infoblox/resource/ipv4address.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Ipv4address < Resource 3 | remote_attr_reader :dhcp_client_identifier, 4 | :ip_address, 5 | :is_conflict, 6 | :lease_state, 7 | :mac_address, 8 | :names, 9 | :network, 10 | :network_view, 11 | :objects, 12 | :status, 13 | :types, 14 | :usage, 15 | :username 16 | 17 | remote_attr_accessor :extattrs, :extensible_attributes 18 | 19 | wapi_object "ipv4address" 20 | 21 | def delete 22 | raise "Not supported" 23 | end 24 | 25 | ## 26 | # as per the WAPI doc: 27 | # This object is created only as part of the record.host object, 28 | # it cannot be created directly. 29 | # 30 | def create 31 | raise "Not supported" 32 | end 33 | 34 | def modify 35 | raise "Not supported" 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Granicus 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/integration/host_spec.rb: -------------------------------------------------------------------------------- 1 | if ENV['INTEGRATION'] 2 | require 'spec_helper' 3 | 4 | describe 'Infoblox::Host' do 5 | describe '.find' do 6 | it 'should find' do 7 | each_version do 8 | # the empty result will be [], so nil is bad. 9 | expect(Infoblox::Host.find(connection, :_max_results => 1)).to_not be_nil 10 | end 11 | end 12 | it 'should create, update, and destroy' do 13 | failure = false 14 | each_version do 15 | @host = Infoblox::Host.new(:connection => connection) 16 | begin 17 | @host.add_ipv4addr('10.30.30.30') 18 | @host.name = "poc-infobloxgem-test1.ep.gdi" 19 | @host.post 20 | 21 | @host = Infoblox::Host.find(connection, {"ipv4addr" => "10.30.30.30"}).first 22 | @host.name = "poc-infobloxgem-test2.ep.gdi" 23 | @host.put 24 | rescue Exception => e 25 | puts e 26 | failure = true 27 | ensure 28 | @host.delete 29 | end 30 | end 31 | fail if failure 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/range_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RangeResponse = Struct.new(:body) 4 | 5 | describe Infoblox::Host, "#next_available_ip" do 6 | it "returns empty array when no IP is available." do 7 | connection = double 8 | uri = Infoblox.base_path + Infoblox::Range.wapi_object 9 | return_json = RangeResponse.new('{"ips": "[]"}') 10 | allow(connection).to receive(:post).with(uri + "?_function=next_available_ip" , {:num=>1, :exclude=>[]}).and_return(return_json) 11 | response = Infoblox::Range.new(:connection => connection) 12 | expect(response.next_available_ip).to eq '[]' 13 | end 14 | 15 | it "gets next available ip from range" do 16 | connection = double 17 | uri = Infoblox.base_path + Infoblox::Range.wapi_object 18 | return_json = RangeResponse.new(' 19 | { 20 | "ips": "[\"1.1.1.1\", \"1.1.1.2\"]" 21 | }') 22 | allow(connection).to receive(:post).with(uri + "?_function=next_available_ip" , {:num=>1, :exclude=>[]}).and_return(return_json) 23 | response = Infoblox::Range.new(:connection => connection) 24 | expect(response.next_available_ip).to eq '["1.1.1.1", "1.1.1.2"]' 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/integration/zone_auth_spec.rb: -------------------------------------------------------------------------------- 1 | if ENV['INTEGRATION'] 2 | require 'spec_helper' 3 | 4 | describe 'Infoblox::ZoneAuth' do 5 | describe '.find' do 6 | it 'should find' do 7 | each_version do 8 | next if Infoblox.wapi_version < '1.1' 9 | # the empty result will be [], so nil is bad. 10 | expect(Infoblox::ZoneAuth.find(connection, :_max_results => 1)).to_not be_nil 11 | end 12 | end 13 | 14 | it 'should create, update, and destroy' do 15 | failure = false 16 | each_version do 17 | next if Infoblox.wapi_version < '1.1' 18 | @zone_auth = Infoblox::ZoneAuth.new(:connection => connection) 19 | begin 20 | @zone_auth.fqdn = "poc-infobloxgem-test1.ep.gdi" 21 | @zone_auth.post 22 | @zone_auth = Infoblox::ZoneAuth.find(connection, {"fqdn" => "poc-infobloxgem-test1.ep.gdi"}).first 23 | @zone_auth.comment = "Test zone auth. Delete me" 24 | @zone_auth.put 25 | rescue Exception => e 26 | puts e 27 | failure = true 28 | ensure 29 | @zone_auth.delete 30 | end 31 | end 32 | fail if failure 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /infoblox.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'infoblox/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "infoblox" 8 | spec.version = Infoblox::VERSION 9 | spec.authors = ["Billy Reisinger"] 10 | spec.email = ["billy.reisinger@govdelivery.com"] 11 | spec.summary = %q{A Ruby wrapper to the Infoblox WAPI} 12 | spec.description = %q{This gem is a Ruby interface to the Infoblox WAPI. Using the gem, you can query, create, update, and delete DNS records in your Infoblox instance.} 13 | spec.homepage = "https://github.com/govdelivery/infoblox" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_runtime_dependency "faraday" 22 | spec.add_runtime_dependency "faraday_middleware" 23 | 24 | spec.add_development_dependency "rspec" 25 | spec.add_development_dependency "bundler" 26 | spec.add_development_dependency "rake" 27 | spec.add_development_dependency "pry" 28 | end 29 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | ## 3.0.0 - June 26, 2019 2 | * Removed 1.8.7 support (should still work, but no longer supported in CI) 3 | * Added new connection timeout option with 60-second default value 4 | 5 | ## 2.0.5 - August 28 2017 6 | * Added ZoneForward resource 7 | 8 | ## 2.0.4 - March 8 2017 9 | * Added RoamingHost resource 10 | * Added fields to FixedAddress 11 | 12 | ## 2.0.3 - February 15 2017 13 | * Added Lease resource 14 | 15 | ## 2.0.2 - February 3 2017 16 | * Move :view to Post only attributes on Arecord 17 | 18 | ## 2.0.1 - October 5 2016 19 | * Add TTL support to CNAME record 20 | 21 | ## 2.0.0 - September 2 2016 22 | Make WAPI version 2.0 the default version. 23 | 24 | ## 1.0.1 - September 2 2016 25 | A point release containing a bugfix and new field on a recod. 26 | 27 | * Don't ask for ipv6addr in the return field for Ptr if the WAPI version is 1.0 28 | * added 'view' attribute to zone_auth setter 29 | * Fix json version for 1.8.7 30 | 31 | ## 1.0.0 - August 15 2016 32 | As implied, this release is a breaking change that modifies the default 33 | behavior for SSL options, specifically by removing the "verify: false" behavior 34 | and requireing the user to supply the configuration when creating an 35 | Infoblox::Connection. 36 | 37 | * Add `ttl` support to the `Txt` record 38 | * Remove `verify: false` from default SSL options 39 | -------------------------------------------------------------------------------- /spec/connection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe Infoblox::Connection do 3 | 4 | ["localhost", "127.0.0.1", "http://localhost:3000", "https://localhost", "http://localhost:3000/"].each do |host| 5 | it "should build URL #{host} without failure" do 6 | conn_params = { 7 | :username => "billy", 8 | :password => "boi", 9 | :host => host 10 | } 11 | uri = "/wapi/v1.0/record:host" 12 | 13 | ic = Infoblox::Connection.new(conn_params) 14 | ic.adapter = :test 15 | ic.adapter_block = stub_get(uri) 16 | 17 | # execute the request. There should be no "URI::BadURIError: both URI are relative" error 18 | ic.get(uri) 19 | end 20 | end 21 | 22 | it "should raise on invalid response" do 23 | host = 'localhost' 24 | conn_params = { 25 | :username => "billy", 26 | :password => "boi", 27 | :host => host 28 | } 29 | uri = "/wapi/v1.0/record:host" 30 | 31 | ic = Infoblox::Connection.new(conn_params) 32 | ic.adapter = :test 33 | ic.adapter_block = stub_get(uri, 404) 34 | 35 | # execute the request. There should be no "URI::BadURIError: both URI are relative" error 36 | 37 | expect { ic.get(uri) }.to raise_error(Infoblox::Error) 38 | end 39 | 40 | def stub_get(uri, status=200) 41 | Proc.new do |stub| 42 | stub.get(uri) { [ status, {}, 'Yay!'] } 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/host_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | HostResponse = Struct.new(:body) 3 | describe Infoblox::Host, "#add_ipv4addr" do 4 | it "adds an address correctly" do 5 | host = Infoblox::Host.new 6 | host.add_ipv4addr("10.10.10.10") 7 | host.add_ipv4addr("10.10.10.12") 8 | expect(host.ipv4addrs[0]).to be_a(Infoblox::HostIpv4addr) 9 | expect(host.ipv4addrs[0].ipv4addr).to eq("10.10.10.10") 10 | end 11 | 12 | it "initializes correctly" do 13 | data = { 14 | :name => 'silly-little-host', 15 | :ipv4addrs => [{:ipv4addr => '10.10.10.10'}] 16 | } 17 | host = Infoblox::Host.new(data) 18 | expect(host.ipv4addrs[0]).to be_a(Infoblox::HostIpv4addr) 19 | expect(host.name).to eq('silly-little-host') 20 | end 21 | 22 | it "posts correctly" do 23 | conn = double 24 | uri = Infoblox.base_path + Infoblox::Host.wapi_object 25 | 26 | allow(conn).to receive(:post).with(uri, { 27 | :ipv4addrs => [{:ipv4addr => "10.10.10.10"}, 28 | {:ipv4addr => "192.168.1.1", 29 | :mac => "109coic0932j3n0293urf"}], 30 | :name => "test-server.test.ing" 31 | }).and_return(HostResponse.new("\"hey\"")) 32 | 33 | h = Infoblox::Host.new(:connection => conn) 34 | h.add_ipv4addr("10.10.10.10") 35 | h.ipv4addrs=([{:ipv4addr => "192.168.1.1", :mac => "109coic0932j3n0293urf"}]) 36 | h.name = "test-server.test.ing" 37 | h.post 38 | expect(h._ref).to eq("hey") 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /spec/search_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | SearchResponse = Struct.new(:body) 3 | 4 | describe Infoblox::Search, ".find" do 5 | it "searches correctly" do 6 | conn = double 7 | 8 | # empty response 9 | return_json = SearchResponse.new("[]") 10 | uri = Infoblox.base_path + Infoblox::Search.wapi_object 11 | allow(conn).to receive(:get).with(uri, {"search_string~" => "foo"}).and_return(return_json) 12 | expect(Infoblox::Search.find(conn, "search_string~" => "foo")).to eq([]) 13 | 14 | # response with host 15 | return_json = SearchResponse.new('[ 16 | { 17 | "_ref": "record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmdkaS50b3BzLmRtei1tYWlsc2VuZGVycy5xYy1nb3ZpcG10YTItZXA:foo-bar-baz.inner.domain/Organization", 18 | "ipv4addrs": [ 19 | { 20 | "_ref": "record:host_ipv4addr/ZG5zLmhvc3RfYWRkcmVzcyQuX2RlZmF1bHQuZ2RpLnRvcHMuZG16LW1haWxzZW5kZXJzLnFjLWdvdmlwbXRhMi1lcC4xNzIuMjQuNC4xODQu:10.10.10.10/foo-bar-baz.inner.domain/Organization", 21 | "configure_for_dhcp": false, 22 | "host": "foo-bar-baz.inner.domain", 23 | "ipv4addr": "10.10.10.10" 24 | } 25 | ], 26 | "name": "foo-bar-baz.inner.domain", 27 | "view": "Organization" 28 | } 29 | ]') 30 | allow(conn).to receive(:get).with(uri, {"search_string~" => "foo"}).and_return(return_json) 31 | result = Infoblox::Search.find(conn, "search_string~" => "foo") 32 | expect(result[0]).to be_a(Infoblox::Host) 33 | host = result[0] 34 | expect(host.name).to eq("foo-bar-baz.inner.domain") 35 | end 36 | end 37 | 38 | -------------------------------------------------------------------------------- /lib/infoblox/resource/host.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Host < Resource 3 | remote_attr_accessor :aliases, 4 | :comment, 5 | :configure_for_dns, 6 | :disable, 7 | :extattrs, 8 | :extensible_attributes, 9 | :ipv4addrs, 10 | :name, 11 | :view 12 | 13 | remote_attr_reader :zone 14 | 15 | wapi_object "record:host" 16 | 17 | ## 18 | # The more robust way to add IP addresses to your host record, 19 | # allowing you to set all the attributes. 20 | # 21 | # Example: 22 | # 23 | # host.ipv4addrs=[{:ipv4addr => '10.10.10.10', :mac => '0x0x0x0x0x0x0x'}] 24 | # 25 | def ipv4addrs=(attrs=[]) 26 | attrs.each do |att| 27 | ipv4addrs << HostIpv4addr.new(att) 28 | end 29 | end 30 | 31 | ## 32 | # Add an IP address to this host. Only allows setting the ipv4addr field 33 | # of the remote HostIpv4Addr record. If you need to set other fields, 34 | # such as mac or configure_for_dns, use #ipv4addrs= 35 | # 36 | def add_ipv4addr(address) 37 | ipv4addrs << HostIpv4addr.new(:ipv4addr => address) 38 | end 39 | 40 | def ipv4addrs 41 | @ipv4addrs ||= [] 42 | end 43 | 44 | def remote_attribute_hash(write=false, post=false) 45 | super.tap do |hsh| 46 | hsh[:ipv4addrs] = ipv4addrs.map do |i| 47 | i.remote_attribute_hash(write, post).delete_if{|k,v| v.nil? } 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # Require this file using `require "spec_helper"` to ensure that it is only 4 | # loaded once. 5 | # 6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 7 | require File.expand_path("../../lib/infoblox", __FILE__) 8 | require 'bundler' 9 | Bundler.require(:test) 10 | if ENV['INTEGRATION'] 11 | require 'highline/import' 12 | end 13 | 14 | module Helper 15 | def each_version 16 | ['1.0', '1.2', '1.4', '2.0'].each do |v| 17 | Infoblox.wapi_version = v 18 | yield 19 | end 20 | ensure 21 | Infoblox.wapi_version = '2.0' 22 | end 23 | 24 | def connection 25 | Infoblox::Connection.new( 26 | :username => $username, 27 | :password => $password, 28 | :host => $host, 29 | :ssl_opts => {:verify => false} 30 | # :logger => Logger.new(STDOUT) 31 | ) 32 | end 33 | end 34 | 35 | RSpec.configure do |config| 36 | # config.treat_symbols_as_metadata_keys_with_true_values = true 37 | config.run_all_when_everything_filtered = true 38 | config.filter_run :focus 39 | 40 | # Run specs in random order to surface order dependencies. If you find an 41 | # order dependency and want to debug it, you can fix the order by providing 42 | # the seed, which is printed after each run. 43 | # --seed 1234 44 | config.order = 'random' 45 | config.include(Helper) 46 | end 47 | 48 | if ENV['INTEGRATION'] 49 | $host = ask("Infoblox host: ") 50 | $username = ask("Infoblox username: ") 51 | $password = ask("Infoblox password: ") {|q| q.echo = false } 52 | end 53 | -------------------------------------------------------------------------------- /lib/infoblox/resource/grid.rb: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/IMPIMBA/infoblox 2 | # Credit where credit is due. 3 | # 4 | module Infoblox 5 | class Grid < Resource 6 | remote_attr_reader :audit_to_syslog_enable, 7 | :external_syslog_server_enable, 8 | :password_setting, 9 | :security_banner_setting, 10 | :security_setting, 11 | :snmp_setting, 12 | :syslog_facility, 13 | :syslog_servers, 14 | :syslog_size 15 | 16 | wapi_object "grid" 17 | 18 | def delete 19 | raise "Not supported" 20 | end 21 | 22 | def create 23 | raise "Not supported" 24 | end 25 | 26 | def modify 27 | raise "Not supported" 28 | end 29 | 30 | def self.get(connection) 31 | JSON.parse(connection.get(resource_uri).body).map do |jobj| 32 | klass = resource_map[jobj["_ref"].split("/").first] 33 | if klass.nil? 34 | puts jobj['_ref'] 35 | warn "umapped resource: #{jobj["_ref"]}" 36 | else 37 | klass.new(jobj.merge({:connection => connection})) 38 | end 39 | end.compact 40 | end 41 | 42 | # Example of a working post for restartservices 43 | # POST /wapi/v1.4/grid/b25lLmNsdXN0ZXIkMA:DNSone?_function=restartservices 44 | # &member_order=SEQUENTIALLY 45 | # &restart_option=RESTART_IF_NEEDED 46 | # &sequential_delay=15 47 | # &service_option=DHCP 48 | def restartservices(member_order="SEQUENTIALLY", 49 | restart_option="RESTART_IF_NEEDED", 50 | sequential_delay=15, 51 | service_option="DHCP") 52 | 53 | post_body = { 54 | :member_order => member_order, 55 | :restart_option => restart_option, 56 | :sequential_delay => sequential_delay.to_i, 57 | :service_option => service_option 58 | } 59 | JSON.parse(connection.post(resource_uri + "?_function=restartservices", post_body).body); 60 | end 61 | 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/infoblox/connection.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Error < StandardError 3 | end 4 | 5 | class Connection 6 | attr_accessor :adapter, 7 | :adapter_block, 8 | :connection, 9 | :host, 10 | :logger, 11 | :password, 12 | :ssl_opts, 13 | :username, 14 | :timeout 15 | 16 | def get(href, params={}) 17 | wrap do 18 | connection.get(href, params) 19 | end 20 | end 21 | 22 | def post(href, body) 23 | wrap do 24 | connection.post do |req| 25 | req.url href 26 | req.body = body.to_json 27 | end 28 | end 29 | end 30 | 31 | def put(href, body) 32 | wrap do 33 | connection.put do |req| 34 | req.url href 35 | req.body = body.to_json 36 | end 37 | end 38 | end 39 | 40 | def delete(href) 41 | wrap do 42 | connection.delete(href) 43 | end 44 | end 45 | 46 | def initialize(opts={}) 47 | self.username = opts[:username] 48 | self.password = opts[:password] 49 | self.host = opts[:host] 50 | self.logger = opts[:logger] 51 | self.ssl_opts = opts[:ssl_opts] || {} 52 | self.timeout = opts[:timeout] || 60 53 | end 54 | 55 | def connection 56 | @connection ||= Faraday.new( 57 | :request => {:timeout => self.timeout, :open_timeout => self.timeout}, 58 | :url => self.host, 59 | :ssl => self.ssl_opts) do |faraday| 60 | faraday.use Faraday::Response::Logger, logger if logger 61 | faraday.request :json 62 | faraday.basic_auth(self.username, self.password) 63 | faraday.adapter(self.adapter, &self.adapter_block) 64 | end 65 | end 66 | 67 | ## 68 | # The host variable is expected to be a protocol with a host name. 69 | # If the host has no protocol, https:// is added before it. 70 | # 71 | def host=(new_host) 72 | unless new_host =~ /^http(s)?:\/\// 73 | new_host = "https://#{new_host}" 74 | end 75 | @host = new_host 76 | end 77 | 78 | def adapter 79 | @adapter ||= :net_http 80 | end 81 | 82 | ## 83 | # Don't display the username/password in logging, etc. 84 | # 85 | def inspect 86 | "#<#{self.class}:#{object_id} @host=\"#{@host}\">" 87 | end 88 | 89 | private 90 | 91 | def wrap 92 | yield.tap do |response| 93 | unless response.status < 300 94 | raise Infoblox::Error.new("Error: #{response.status} #{response.body}") 95 | end 96 | end 97 | end 98 | end 99 | end 100 | 101 | -------------------------------------------------------------------------------- /spec/resource_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | class FooResource < Infoblox::Resource 3 | remote_attr_accessor :name, :junction, :extattrs, :extensible_attributes 4 | remote_attr_writer :do_it 5 | remote_post_accessor :sect 6 | remote_attr_reader :readonly_thing 7 | attr_accessor :animal 8 | wapi_object "foo:animal" 9 | end 10 | 11 | FooResponse = Struct.new(:body) 12 | 13 | describe Infoblox::Resource, "#add_ipv4addr" do 14 | it "hashes correctly" do 15 | host = FooResource.new 16 | host._ref = "123" 17 | host.animal = "mom" 18 | host.name = "lol" 19 | hsh = host.send(:remote_attribute_hash) 20 | expect(hsh).to eq({:name => 'lol'}) 21 | end 22 | 23 | it "handles extattrs correctly in return fields" do 24 | expect(Infoblox).to receive(:wapi_version).and_return("1.0") 25 | hsh = FooResource._return_fields 26 | expect(hsh).to eq('name,junction,extensible_attributes,readonly_thing') 27 | 28 | expect(Infoblox).to receive(:wapi_version).and_return("1.2") 29 | hsh = FooResource._return_fields 30 | expect(hsh).to eq('name,junction,extattrs,readonly_thing') 31 | 32 | expect(Infoblox).to receive(:wapi_version).and_return("2.0") 33 | hsh = FooResource._return_fields 34 | expect(hsh).to eq('name,junction,extattrs,readonly_thing') 35 | end 36 | 37 | it "should have a correct resource_uri" do 38 | expect(FooResource.resource_uri).to eq(Infoblox.base_path + "foo:animal") 39 | f=FooResource.new 40 | expect(f.resource_uri).to eq(Infoblox.base_path + "foo:animal") 41 | f._ref = "lkjlkj" 42 | expect(f.resource_uri).to eq(Infoblox.base_path + "lkjlkj") 43 | end 44 | 45 | it "should find with default attributes" do 46 | conn = double 47 | each_version do 48 | uri = Infoblox.base_path + "foo:animal" 49 | if(Infoblox.wapi_version >= '1.2') 50 | allow(conn).to receive(:get).with(uri, {:_return_fields => "name,junction,extattrs,readonly_thing"}).and_return(FooResponse.new("[]")) 51 | else 52 | allow(conn).to receive(:get).with(uri, {:_return_fields => "name,junction,extensible_attributes,readonly_thing"}).and_return(FooResponse.new("[]")) 53 | end 54 | expect(FooResource.all(conn)).to eq([]) 55 | end 56 | end 57 | 58 | it "should allow .all with return fields or max results" do 59 | conn = double 60 | uri = Infoblox.base_path + "foo:animal" 61 | each_version do 62 | if Infoblox.wapi_version >= '1.2' 63 | allow(conn).to receive(:get).with(uri, {:_return_fields => "name,junction,extattrs,readonly_thing", :_max_results => -70}).and_return(FooResponse.new("[]")) 64 | else 65 | allow(conn).to receive(:get).with(uri, {:_return_fields => "name,junction,extensible_attributes,readonly_thing", :_max_results => -70}).and_return(FooResponse.new("[]")) 66 | end 67 | end 68 | expect(FooResource.all(conn, :_max_results => -70)).to eq([]) 69 | end 70 | 71 | it "should put with the right attributes" do 72 | conn = double 73 | uri = Infoblox.base_path + "abcd" 74 | allow(conn).to receive(:put).with(uri, {:name => "jerry", :junction => "32", :do_it => false}).and_return(FooResponse.new("\"foobar\"")) 75 | f = FooResource.new(:connection => conn) 76 | f._ref = "abcd" 77 | f.do_it = false 78 | f.junction = "32" 79 | f.name = "jerry" 80 | f.sect = :fulburns 81 | f.put 82 | expect(f._ref).to eq("foobar") 83 | end 84 | 85 | it "should post with the right attributes" do 86 | conn = double 87 | uri = Infoblox.base_path + "foo:animal" 88 | allow(conn).to receive(:post).with(uri, {:name => "jerry", :junction => "32", :do_it => false, :sect => :fulburns}).and_return(FooResponse.new("\"abcdefg\"")) 89 | f = FooResource.new(:connection => conn) 90 | f.do_it = false 91 | f.junction = "32" 92 | f.name = "jerry" 93 | f.sect = :fulburns 94 | f.post 95 | expect(f._ref).to eq('abcdefg') 96 | end 97 | 98 | it 'should set all attributes including readonly attrs' do 99 | f = FooResource.new(:readonly_thing => 45, :do_it => false, :sect => :larry) 100 | expect(f.readonly_thing).to eq(45) 101 | expect(f.do_it).to be(false) 102 | expect(f.sect).to eq(:larry) 103 | end 104 | 105 | it 'should load attributes on get' do 106 | conn = double 107 | uri = Infoblox.base_path + "a:ref:that:is:fake" 108 | json = {:name => "john", :junction => "hi", :extattrs => {"foo" => 3}}.to_json 109 | response = FooResponse.new(json) 110 | expect(conn).to receive(:get).with(uri, FooResource.default_params).and_return(response) 111 | f = FooResource.new(:connection => conn, :_ref => "a:ref:that:is:fake") 112 | f.get 113 | expect(f.name).to eq("john") 114 | expect(f.junction).to eq("hi") 115 | expect(f.extattrs).to eq({"foo" => 3}) 116 | end 117 | 118 | it 'should map wapi objects to classes' do 119 | @expected = {} 120 | ObjectSpace.each_object(Class) do |p| 121 | if p < Infoblox::Resource 122 | @expected[p.wapi_object] = p 123 | end 124 | end 125 | expect(Infoblox::Resource.resource_map).to eq(@expected) 126 | end 127 | end 128 | 129 | -------------------------------------------------------------------------------- /lib/infoblox/resource.rb: -------------------------------------------------------------------------------- 1 | module Infoblox 2 | class Resource 3 | attr_accessor :_ref, :connection 4 | 5 | def self.wapi_object(obj=nil) 6 | if obj.nil? 7 | @wapi_object 8 | else 9 | self.resource_map[obj] = self 10 | @wapi_object = obj 11 | end 12 | end 13 | 14 | ## 15 | # Define a writeable remote attribute, i.e. one that 16 | # should show up in post / put operations. 17 | # 18 | def self.remote_attr_accessor(*args) 19 | args.each do |a| 20 | attr_accessor a 21 | remote_attrs << a 22 | end 23 | end 24 | 25 | ## 26 | # Define a remote attribute that can only be sent during a 27 | # POST operation. 28 | # 29 | def self.remote_post_accessor(*args) 30 | args.each do |a| 31 | attr_accessor a 32 | remote_post_attrs << a 33 | end 34 | end 35 | 36 | ## 37 | # Define a remote attribute that is write-only 38 | # 39 | def self.remote_attr_writer(*args) 40 | args.each do |a| 41 | attr_accessor a 42 | remote_write_only_attrs << a 43 | end 44 | end 45 | 46 | ## 47 | # Define a read-only attribute 48 | # 49 | def self.remote_attr_reader(*args) 50 | args.each do |a| 51 | attr_reader a 52 | remote_read_only_attrs << a 53 | end 54 | end 55 | 56 | def self.remote_attrs 57 | @remote_attrs ||= [] 58 | end 59 | 60 | def self.remote_write_only_attrs 61 | @remote_write_only_attrs ||= [] 62 | end 63 | 64 | def self.remote_read_only_attrs 65 | @remote_read_only_attrs ||= [] 66 | end 67 | 68 | def self.remote_post_attrs 69 | @remote_post_attrs ||= [] 70 | end 71 | 72 | def self._return_fields 73 | remove = Infoblox.wapi_version < '1.2' ? :extattrs : :extensible_attributes 74 | ((self.remote_attrs + self.remote_read_only_attrs) - [remove]).join(",") 75 | end 76 | 77 | def self.default_params 78 | {:_return_fields => self._return_fields} 79 | end 80 | 81 | ## 82 | # Return an array of all records for this resource. You can use the default parameters 83 | # _max_results an/or _return_fields as documented by Infoblox. 84 | # 85 | # Example: return only 70 results 86 | # {"_max_results" => 70} 87 | # 88 | # Example: return only 100 results, throw an error if there are more 89 | # {"_max_results" => -100} 90 | # 91 | def self.all(connection, params = {}) 92 | params = default_params.merge(params) 93 | JSON.parse(connection.get(resource_uri, params).body).map do |item| 94 | new(item.merge({:connection => connection})) 95 | end 96 | end 97 | 98 | ## 99 | # Find resources with query parameters. You can use the default parameters 100 | # _max_results an/or _return_fields as documented by Infoblox. 101 | # 102 | # Example: return extensible attributes for every resource. 103 | # {"_return_fields" => "extensible_attributes"} 104 | # 105 | # Example: filter resources by name, return 692 results or less 106 | # {"name~" => "foo.*bar", "_max_results" => 692} 107 | # 108 | def self.find(connection, params) 109 | params = default_params.merge(params) 110 | JSON.parse(connection.get(resource_uri, params).body).map do |item| 111 | new(item.merge({:connection => connection})) 112 | end 113 | end 114 | 115 | def self.resource_uri 116 | Infoblox.base_path + self.wapi_object 117 | end 118 | 119 | ## 120 | # A hash that maps Infoblox WAPI object identifiers to subclasses of Resource. 121 | # Used by the Search resource for mapping response objects. 122 | # 123 | def self.resource_map 124 | @@resource_map ||= {} 125 | end 126 | 127 | def initialize(attrs={}) 128 | load_attributes(attrs) 129 | end 130 | 131 | def post 132 | self._ref = unquote(connection.post(resource_uri, remote_attribute_hash(write = true, post = true)).body) 133 | true 134 | end 135 | alias_method :create, :post 136 | 137 | def delete 138 | connection.delete(resource_uri).status == 200 139 | end 140 | 141 | def get(params=self.class.default_params) 142 | response = connection.get(resource_uri, params).body 143 | load_attributes(JSON.parse(response)) 144 | self 145 | end 146 | 147 | def put 148 | self._ref = unquote(connection.put(resource_uri, remote_attribute_hash(write = true)).body) 149 | true 150 | end 151 | 152 | def resource_uri 153 | self._ref.nil? ? self.class.resource_uri : (Infoblox.base_path + self._ref) 154 | end 155 | 156 | def remote_attribute_hash(write=false, post=false) 157 | {}.tap do |hsh| 158 | self.class.remote_attrs.each do |k| 159 | hsh[k] = self.send(k) unless self.send(k).nil? 160 | end 161 | self.class.remote_write_only_attrs.each do |k| 162 | hsh[k] = self.send(k) unless self.send(k).nil? 163 | end if write 164 | self.class.remote_post_attrs.each do |k| 165 | hsh[k] = self.send(k) unless self.send(k).nil? 166 | end if post 167 | end 168 | end 169 | 170 | private 171 | def unquote(str) 172 | str.gsub(/\A['"]+|['"]+\Z/, "") 173 | end 174 | 175 | def load_attributes(attrs) 176 | attrs.each do |k,v| 177 | # Some things have specialized writers, 178 | # like Host 179 | if respond_to?("#{k}=") 180 | send("#{k}=", v) 181 | 182 | # Some things don't have writers (i.e. remote_attr_reader fields) 183 | else 184 | instance_variable_set("@#{k}", v) 185 | end 186 | end 187 | end 188 | end 189 | 190 | end 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Infoblox 2 | 3 | Interact with the Infoblox WAPI with Ruby. Use this gem to list, create, and delete host records. 4 | 5 | [![Build Status](https://travis-ci.org/govdelivery/infoblox.svg?branch=master)](https://travis-ci.org/govdelivery/infoblox) 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | gem 'infoblox' 11 | 12 | And then execute: 13 | 14 | $ bundle 15 | 16 | Or install it yourself as: 17 | 18 | $ gem install infoblox 19 | 20 | # Usage 21 | 22 | ## Connecting 23 | Before operating against Infoblox, an instance of the `Infoblox::Connection` class is necessary: 24 | 25 | connection = Infoblox::Connection.new(username: '', password: '', host: '') 26 | 27 | You can specify SSL options by using the `ssl_opts` option. The options are passed to the HTTP adapter, which is `net/http` by default. 28 | 29 | connection = Infoblox::Connection.new(username: '', password: '', host: '', ssl_opts: {verify: false}) 30 | 31 | You can supply a logger to get debugging information: 32 | 33 | connection = Infoblox::Connection.new(username: '', password: '', host: '', logger: Logger.new(STDOUT)) 34 | 35 | It is possible to set timeout, read and write timeout: 36 | 37 | connection = Infoblox::Connection.new(username: '', password: '', host: '', timeout: 120) 38 | 39 | ## Reading 40 | Each resource class implements `.all`, `.find`, and `#get`. 41 | 42 | ### `.find` 43 | Use find when you don't know what you are looking for or you don't have a `_ref` saved (see `get` below). Use `_max_results` to limit / expand the number of returned results. 44 | 45 | # You can find hosts that match a regular expression: 46 | Infoblox::Host.find(connection, {"name~" => "demo[0-9]{1,}-web.domain"}) 47 | # => [...] 48 | 49 | ### `.all` 50 | Show all results (note that this is limited by `_max_results`). Use this cautiously when you have a large potential set of results. 51 | 52 | # Find all networks. Note that this is limited to 1000 objects, as per the 53 | # Infoblox WAPI documentation. 54 | Infoblox::Network.all(connection) 55 | # => [...] 56 | 57 | # If you want more than 1,000 records, use `_max_results`: 58 | Infoblox::Network.all(connection, _max_results: 7890) 59 | 60 | The usage of search parameters is well-documented in the Infoblox WAPI documentation, and this client supports them fully. 61 | 62 | ### `#get` 63 | Use this when you have saved a reference (`_ref`) and want to load it later. 64 | 65 | host = Infoblox::Host.new(:connection => c, :_ref => ref).get 66 | puts host.name 67 | # => foo.bar 68 | 69 | ## Searching 70 | You can also search across all Infoblox resource types using the `Infoblox::Search` resource. The response will contain any number of `Infoblox::Resource` subclass instances. 71 | 72 | result = Infoblox::Search.find(connection, "search_string~" => "webserver-") 73 | # => [#, #, ...] 74 | 75 | ## Writing and deleting 76 | The resource class instances implement `get`, `post`, `put`, and `delete` methods, which correspond to the REST verbs supported by the WAPI. For example, here is how to create, update, and delete a network: 77 | 78 | # create 79 | network = Infoblox::Network.new(connection: connection) 80 | network.network = "10.20.30.0/24" 81 | network.extensible_attributes = {"VLAN" => "my_vlan"} 82 | network.auto_create_reversezone = true 83 | network.post 84 | 85 | # update 86 | network.network = "10.20.31.0/24" 87 | network.put 88 | 89 | # delete 90 | network.delete 91 | 92 | This pattern applies for interacting with every resource. Supported resources include: 93 | 94 | Infoblox::AAAArecord 95 | Infoblox::Arecord 96 | Infoblox::Cname 97 | Infoblox::Fixedaddress 98 | Infoblox::Grid 99 | Infoblox::Host 100 | Infoblox::HostIpv4addr 101 | Infoblox::Ipv4address 102 | Infoblox::Lease 103 | Infoblox::Mx 104 | Infoblox::Network 105 | Infoblox::NetworkContainer 106 | Infoblox::Ptr 107 | Infoblox::Range 108 | Infoblox::RoamingHost 109 | Infoblox::Search 110 | Infoblox::Srv 111 | Infoblox::Txt 112 | Infoblox::ZoneAuth 113 | Infoblox::ZoneForward 114 | 115 | The specific attributes supported by each resource are listed in the source code. Adding a new resource class is easy, and pull requests are encouraged. 116 | 117 | ## Changing IP on an existing host 118 | To change the IP of an existing host, you have to poke around in the ipv4addrs collection to find the one you are looking for. The example below assumes that there is only one ipv4 address and just overwrites it. 119 | 120 | host = Infoblox::Host.find(connection, {"name~" => "my.host.name"}).first 121 | host.ipv4addrs[0].ipv4addr = "10.10.10.10" 122 | host.put 123 | 124 | ## Basic CRUD examples 125 | 126 | To do basic create/update/delete operations on an a_record/ptr_record set: 127 | 128 | Create: 129 | 130 | a_record = Infoblox::Arecord.new( 131 | connection: connection, 132 | name: , 133 | ipv4addr: ) 134 | a_record.post 135 | 136 | ptr_record = Infoblox::Ptr.new( 137 | connection: connection, 138 | ptrdname: , 139 | ipv4addr: ) 140 | ptr_record.post 141 | 142 | Update: 143 | 144 | a_record = Infoblox::Arecord.find(connection, { 145 | name: , 146 | ipv4addr: 147 | }).first 148 | a_record.name = 149 | a_record.ipv4addr = 150 | a_record.view = nil 151 | a_record.put 152 | 153 | ptr_record = Infoblox::Ptr.find(connection, { 154 | ptrdname: , 155 | ipv4addr: 156 | }).first 157 | 158 | ptr_record.ptrdname = 159 | ptr_record.ipv4addr = 160 | ptr_record.view = nil 161 | ptr_record.put 162 | 163 | Delete: 164 | 165 | a_record = Infoblox::Arecord.find(connection, { 166 | name: , 167 | ipv4addr: 168 | }).first 169 | a_record.delete 170 | 171 | ptr_record = Infoblox::Ptr.find(connection, { 172 | ptrdname: , 173 | ipv4addr: 174 | }).first 175 | ptr_record.delete 176 | 177 | ## Next Available IP 178 | 179 | The `Infoblox::Network` and `Infoblox::Range` objects support the `next_available_ip` WAPI function: 180 | 181 | network = Infoblox::Network.find(connection, 182 | network: '10.21.0.0/24').first 183 | puts network.next_available_ip.inspect 184 | #=> ["10.21.0.22"] 185 | 186 | Note that this function does not work on a resource that has not been created. In other words, if you want to get the next available IP for a given network segment, you have to create that segment beforehand. See the CRUD examples above. 187 | 188 | 189 | ## Extensible Attributes 190 | 191 | Extensible attributes are supported in this client. It should be noted that in WAPI versions before 1.2, the field is named "extensible_attributes", whereas in version 1.2 and later, it is named "extattrs". 192 | 193 | ## Infoblox Version Compatibility 194 | 195 | This gem is known to be compatible with Infoblox versions 1.0 through 2.0. While Infoblox claims that their API is backwards-compatible, one caveat remains with the Extensible Attributes (see elsewhere in this document). Some features are only available in newer versions (such as Fixedaddress and AAAARecord). For best results, set the version using the `WAPI_VERSION` environment variable. For example: 196 | 197 | WAPI_VERSION=1.2 ruby my_script.rb 198 | 199 | The default version is 2.0. 200 | 201 | ## Ruby Version Compatibility 202 | 203 | This gem is tested against Ruby versions 1.9.3, 2.1.6, JRuby-head, and JRuby in 1.9 mode. 204 | 205 | The last compatible version with Ruby 1.8.x is 2.0.5. 206 | 207 | ## Development / testing 208 | 209 | First, clone and bundle: 210 | 211 | bundle install 212 | 213 | To run the tests: 214 | 215 | rspec 216 | 217 | To run the integration tests (you will be prompted for your Infoblox credentials): 218 | 219 | INTEGRATION=true bundle 220 | INTEGRATION=true rspec 221 | 222 | Please note that the integration tests do not work in Ruby 1.8.7, but the unit tests function normally. 223 | 224 | ## Contributing 225 | 226 | 1. Fork it 227 | 2. Create your feature branch (`git checkout -b my-new-feature`) 228 | 3. Commit your changes (`git commit -am 'Add some feature'`) 229 | 4. Push to the branch (`git push origin my-new-feature`) 230 | 5. Create new Pull Request 231 | --------------------------------------------------------------------------------