audiolize/vagrant-softlayer

View on GitHub
contrib/vagrant-softlayer-vlans

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby

require 'csv'
require 'getoptlong'
require 'json'
require 'pp'

require 'rubygems'
require 'softlayer_api'

class SoftLayerVlans
  def initialize(sl_api_key, sl_username, sl_endpoint_url = SoftLayer::API_PUBLIC_ENDPOINT, sl_timeout = 60)
    if ! [ SoftLayer::API_PUBLIC_ENDPOINT, SoftLayer::API_PRIVATE_ENDPOINT ].include?(sl_endpoint_url)
      raise ArgumentError, "Invalid SoftLayer Endpoint URL provided, expected one of #{ [ SoftLayer::API_PUBLIC_ENDPOINT, SoftLayer::API_PRIVATE_ENDPOINT ].inspect }: #{ sl_endpoint_url.inspect }"
    end

    @sl_credentials = {
      :api_key      => sl_api_key,
      :endpoint_url => sl_endpoint_url,
      :timeout      => sl_timeout,
      :user_agent   => "vagrant-softlayer",
      :username     => sl_username
    }

    begin
      @sl_services = {
        :account => SoftLayer::Client.new(@sl_credentials)["SoftLayer_Account"]
      }
    rescue Exception => e
      raise Exception, "Failed to initialize SoftLayer Account service for retrieving network vlan details: #{ e.message }"
    end
  end

  def [](vlan_name)
    vlan_details = vlans(:vlan_name => vlan_name)
    
    raise KeyError, "Invalid vlan name provided as a SoftLayerVlans key: #{ vlan_name.inspect }" if vlan_details.empty?
    
    return vlan_details[0]
  end

  def has_vlan?(vlan_name)
    vlan_details = vlans(:vlan_name => vlan_name)

    return ! vlan_details.empty?
  end

  def vlans(filter = { :datacenter_name => nil, :vlan_name => nil, :vlan_space => nil })
    vlan_details = []

    if filter.class != Hash
      raise TypeError, "Invalid value specified for filter, expected hash of filter values #{ { :datacenter_name => nil, :vlan_name => nil, :vlan_space => nil }.inspect }: #{ filter.inspect }"
    end

    if ! filter.keys.select{|vlan_property| ! [ :datacenter_name, :vlan_name, :vlan_space ].include?(vlan_property)}.empty?
      raise KeyError, "Invalid vlan property used as filter key, expected one of #{ [ :datacenter_name, :vlan_name, :vlan_space ].inspect }: #{ filter.keys.inspect }"
    end

    if filter.has_key?(:datacenter_name) && ! filter[:datacenter_name].nil? && ! [Array, String].include?(filter[:datacenter_name].class)
      raise TypeError, "Invalid type for filter key :datacenter_name, must be a Array or String of datacenter name(s): #{ filter[:datacenter_name].class }"
    end
    
    if filter.has_key?(:vlan_name) && ! filter[:vlan_name].nil? && ! [ Array, String ].include?(filter[:vlan_name].class)
      raise TypeError, "Invalid type for filter key :vlan_name, must be a Array or String of vlan name(s): #{ filter[:vlan_name].class }"
    end

    if filter.has_key?(:vlan_space) && ! filter[:vlan_space].nil? && ! [ nil, :public, :private ].include?(filter[:vlan_space])
      raise ArgumentError, "Invalid value for filter key :vlan_space, expected one of #{ [ nil, :public, :private ].inspect }: #{ filter[:vlan_space].inspect }"
    end

    routers = @sl_services[:account].object_mask("mask[routers,routers.datacenter,routers.networkVlans,routers.networkVlans.networkSpace,routers.networkVlans.type]").getObject["routers"]

    routers.each do |router|
      router["networkVlans"].each do |vlan|
        vlan_details.push({
                            :datacenter     => router["datacenter"]["name"],
                            :id             => vlan["id"],
                            :name           => vlan["name"],
                            :qualified_name => [ router["hostname"].split('.').reverse.join('.'), vlan["vlanNumber"] ].join('.'),
                            :space          => vlan["networkSpace"].to_s.downcase
                          })
      end
    end
    
    if filter.has_key?(:datacenter_name) && ! filter[:datacenter_name].nil?
      vlan_details.delete_if {|vlan| (filter[:datacenter_name].class == Array ?
                                      (! filter[:datacenter_name].include?(vlan[:datacenter])) :
                                      (filter[:datacenter_name] != vlan[:datacenter])
                                      )}
    end

    if filter.has_key?(:vlan_name) && ! filter[:vlan_name].nil?
      vlan_details.delete_if {|vlan| (filter[:vlan_name].class == Array ?
                                      (! (filter[:vlan_name].include?(vlan[:name]) || filter[:vlan_name].include?(vlan[:qualified_name]))) :
                                      (filter[:vlan_name] != vlan[:name] && filter[:vlan_name] != vlan[:qualified_name])
                                      )}
    end

    vlan_details.delete_if {|vlan| filter[:vlan_space].to_s != vlan[:space]} if filter.has_key?(:vlan_space) && ! filter[:vlan_space].nil?

    return vlan_details
  end
end

class VagrantSoftLayerVlans
  def initialize()
    @cli_opts = [
                 [ '--datacenter_name', '-d', GetoptLong::REQUIRED_ARGUMENT ],
                 [ '--format',          '-f', GetoptLong::REQUIRED_ARGUMENT ],
                 [ '--help',            '-h', GetoptLong::NO_ARGUMENT       ],
                 [ '--sl_api_key',      '-k', GetoptLong::REQUIRED_ARGUMENT ],
                 [ '--sl_endpoint_url', '-e', GetoptLong::REQUIRED_ARGUMENT ],
                 [ '--sl_timeout',      '-t', GetoptLong::REQUIRED_ARGUMENT ],
                 [ '--sl_username',     '-u', GetoptLong::REQUIRED_ARGUMENT ],
                 [ '--vlan_name',       '-v', GetoptLong::REQUIRED_ARGUMENT ],
                 [ '--vlan_space',      '-s', GetoptLong::REQUIRED_ARGUMENT ]
                ]
    @config   = {
      :columns         => [ :id, :name, :qualified_name, :space, :datacenter ],
      :column_labels   => {
        :datacenter     => 'datacenter',
        :id             => 'id',
        :name           => 'name',
        :qualified_name => 'qualified name',
        :space          => 'space'
      },
      :datacenter_name => nil,
      :format          => :pretty,
      :formats         => [ :csv, :json, :perty, :pretty, :raw ],
      :sl_credentials  => {
        :api_key       => nil,
        :endpoint_url  => SoftLayer::API_PUBLIC_ENDPOINT,
        :timeout       => 60,
        :username      => nil
      },
      :vlan_name       => nil,
      :vlan_space      => nil
    }
    @help     = <<-EOF
vagrant-softlayer-vlans [OPTION]

--datacenter_name|-d DCNAME,...:
    Comma separated list of datacenter names to filter vlan results on.

--format|-f FORMAT:
    Sets the output format of the vlan results. Acceptable values are 'csv', 'json', 'perty', 'pretty', or 'raw'.
    Defaults to 'pretty'.

--help|-h:
    Print this help.

--sl_api_key|-k SL_API_KEY
    Sets the SoftLayer API key. If not specified, it is assumed SL_API_KEY environment variable is set.

--sl_endpoint_url|-e SL_API_BASE_URL
    Sets the SoftLayer endpoint URL. If not specified, it assumed SL_API_BASE_URL environment variable is set to API_PUBLIC_ENDPOINT or API_PRIVATE_ENDPOINT.
    Defaults to API_PUBLIC_ENDPOINT.

--sl_timeout|-t SL_TIMEOUT
    Sets the SoftLayer API call timeout value in seconds.

--sl_username|-u USERNAME
    Sets the SoftLayer account user name. If not specified, it is assumed SL_API_USERNAME environment variable is set.

--vlan_name|-v VLAN_NAME,...
    Comma separated list of vlan names to filter vlan results on. A vlan name can be the vlan name or qualified name.

--vlan_space|-s VLAN_SPACE
    Sets the vlan space to filter  vlan results on. Acceptable spaces are 'public' or 'private'.

EOF
  end

  def run()
    proc_cli_options

    begin
      sl_vlans = SoftLayerVlans.new(@config[:sl_credentials][:api_key], @config[:sl_credentials][:username], @config[:sl_credentials][:endpoint_url], @config[:sl_credentials][:timeout])
    rescue Exception => e
      $stderr.puts "ERROR: Failed to instantiate SoftLayerVlans instance: #{ e.message }"
      exit 1
    end

    begin
      vlan_details = sl_vlans.vlans(:datacenter_name => @config[:datacenter_name], :vlan_name => @config[:vlan_name], :vlan_space => @config[:vlan_space])
    rescue Exception => e
      $stderr.puts "ERROR: Failed to retrieve SoftLayer account vlans: #{ e.message }"
      exit 1
    end

    print_vlans(vlan_details) if ! vlan_details.empty?
  end

  private

  def proc_cli_options()
    begin
      opts       = GetoptLong.new(*@cli_opts)
      opts.quiet = true

      opts.each do |opt, optval|
        case opt
        when '--datacenter_name'
          @config[:datacenter_name] = optval.to_s.split(',')

        when '--format'
          if ! @config[:formats].include?(optval.to_s.downcase.to_sym)
            $stderr.puts "ERROR: Invalid format value, expected one of #{ @config[:formats].map{|format| format.to_s}.inspect }: #{ optval.inspect }"
            exit 2
          end

          @config[:format] = optval.to_s.downcase.to_sym

        when '--help'
          puts @help
          exit 0

        when '--sl_api_key'
          @config[:sl_credentials][:api_key] = optval.to_s
          
        when '--sl_endpoint_url'
          if ! [ "API_PUBLIC_ENDPOINT", "API_PRIVATE_ENDPOINT" ].include?(optval.to_s.upcase)
            $stderr.puts "ERROR: Invalid endpoint_url value: " + optval.to_s.upcase
            exit 2
          end
          
          @config[:sl_credentials][:endpoint_url] = (optval.to_s.upcase == 'API_PUBLIC_ENDPOINT' ? SoftLayer::API_PUBLIC_ENDPOINT : SoftLayer::API_PRIVATE_ENDPOINT)

        when '--sl_timeoout'
          @config[:sl_credentials][:timeout] = optval.to_i

        when '--sl_username'
          @config[:sl_credentials][:username] = optval.to_s

        when '--vlan_name'
          @config[:vlan_name] = optval.to_s.split(',')

        when '--vlan_space'
          if ! [ :private, :public ].include?(optval.to_s.downcase.to_sym)
            $stderr.puts "ERROR: Invalid vlan space value, expected one of #{ [ 'private', 'public' ].inspect }: #{ optval.inspect }"
            exit 2
          end

          @config[:vlan_space] = optval.to_s.downcase.to_sym

        end
      end
    rescue GetoptLong::Error => e
      $stderr.puts "vagrant-softlayer-vlans failed to process cli options: #{ e.message }"
      exit 1
    end

    @config[:sl_credentials][:username] = ENV["SL_API_USERNAME"] if @config[:sl_credentials][:username].nil? && ENV.include?("SL_API_USERNAME")
    @config[:sl_credentials][:api_key]  = ENV["SL_API_KEY"]      if @config[:sl_credentials][:api_key].nil?  && ENV.include?("SL_API_KEY")

    if @config[:sl_credentials][:endpoint_url].nil? && ENV.include?("SL_API_BASE_URL")
      if ! [ 'API_PRIVATE_ENDPOINT', 'API_PUBLIC_ENDPOINT' ].include?(ENV["SL_API_BASE_URL"])
        $stderr.puts "ERROR: Invalid SoftLayer endpoint URL specified in environment variable SL_API_BASE_URL, expected one of #{ [ 'API_PRIVATE_ENDPOINT', 'API_PUBLIC_ENDPOINT' ].inspect }: #{ ENV["SL_API_BASE_URL"].inspect }"
        exit 2
      end

      @config[:sl_credentials][:endpoint_url] = (ENV["SL_API_BASE_URL"] == "API_PUBLIC_ENDPOINT" ? SoftLayer::API_PUBLIC_ENDPOINT : SoftLayer::API_PRIVATE_ENDPOINT)
    end
    
    if @config[:sl_credentials][:username].nil?
      $stderr.puts "ERROR: No SoftLayer username specified"
      exit 2
    end
    
    if @config[:sl_credentials][:username].nil?
      $stderr.puts "ERROR: No SoftLayer user name specified"
      exit 2
    end
    
    if @config[:sl_credentials][:api_key].nil?
      $stderr.puts "ERROR: No SoftLayer API key specified"
      exit 2
    end
  end

  def print_vlans(vlan_details)
    case @config[:format]
    when :csv
      puts @config[:columns].map{|col| @config[:column_labels][col]}.to_csv(:force_quotes => true)
      
      vlan_details.each do |vlan|
        csv_row = []

        @config[:columns].each {|col| csv_row.push(vlan[col])}

        puts csv_row.to_csv(:force_quotes => true)
      end

    when :json 
      puts JSON.pretty_generate(vlan_details)

    when :perty, :pretty
      table = []
      
      table.push(@config[:columns].map{|col| @config[:column_labels][col]})
      
      vlan_details.each {|vlan| table.push(@config[:columns].map {|col| vlan[col].to_s})}
      
      max_col_widths = table.transpose.map{|col| col.group_by(&:size).max.first + 2}

      puts ':' + max_col_widths.map{|col_width| '.' * col_width}.join(':') + ':'

      print ':'

      table[0].each_index {|col| print table[0][col].center(max_col_widths[col]) + ':'}

      puts

      puts ':' + max_col_widths.map{|colWidth| '.' * colWidth}.join(':') + ':'

      table.shift

      table.each do |row|
        print ':'
        
        row.each_index {|col| print row[col].center(max_col_widths[col]) + ':'}

        puts
      end

      puts ':' + max_col_widths.map{|col_width| '.' * col_width}.join(':') + ':'

    when :raw
      pp vlan_details

    end
  end
end

if __FILE__ == $0
  VagrantSoftLayerVlans.new.run()
end