nledez/proxmox

View on GitHub
lib/proxmox.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# encoding: utf-8

require 'proxmox/version'
require 'rest_client'
require 'json'

# This module encapsulates ability to manage Proxmox server
module Proxmox
  # Object to manage Proxmox server
  class Proxmox
    # Return connection status
    # - connected
    # - error
    attr_reader :connection_status

    # Create a object to manage a Proxmox server through API
    #
    # :call-seq:
    #   new(pve_cluster, node, username, password, realm, ssl_options) -> Proxmox
    #
    # Example:
    #
    #   Proxmox::Proxmox.new('https://the-proxmox-server:8006/api2/json/', 'node', 'root', 'secret', 'pam', {verify_ssl: false})
    #
    def initialize(pve_cluster, node, username, password, realm, ssl_options = {})
      @pve_cluster = pve_cluster
      @node = node
      @username = username
      @password = password
      @realm = realm
      @ssl_options = ssl_options
      @connection_status = 'error'
      @site = RestClient::Resource.new(@pve_cluster, @ssl_options)
      @auth_params = create_ticket
    end

    def get(path, args = {})
      http_action_get(path, args)
    end

    def post(path, args = {})
      http_action_post(path, args)
    end

    def put(path, args = {})
      http_action_put(path, args)
    end

    def delete(path)
      http_action_delete(path)
    end

    # Get task status
    #
    # :call-seq:
    #   task_status(task-id) -> String
    #
    # - taksstatus
    # - taskstatus:exitstatus
    #
    # Example:
    #
    #   taskstatus 'UPID:localhost:00051DA0:119EAABC:521CCB19:vzcreate:203:root@pam:'
    #
    # Examples return:
    #   - running
    #   - stopped:OK
    #
    def task_status(upid)
      data = http_action_get "nodes/#{@node}/tasks/#{URI.encode upid}/status"
      status = data['status']
      exitstatus = data['exitstatus']
      if exitstatus
        "#{status}:#{exitstatus}"
      else
        "#{status}"
      end
    end

    # Get template list
    #
    # :call-seq:
    #   templates -> Hash
    #
    # Return a Hash of all templates
    #
    # Example:
    #
    #   templates
    #
    # Example return:
    #
    #   {
    #     'ubuntu-10.04-standard_10.04-4_i386' => {
    #         'format' => 'tgz',
    #         'content' => 'vztmpl',
    #         'volid' => 'local:vztmpl/ubuntu-10.04-standard_10.04-4_i386.tar.gz',
    #         'size' => 142126884
    #     },
    #     'ubuntu-12.04-standard_12.04-1_i386' => {
    #         'format' => 'tgz',
    #         'content' => 'vztmpl',
    #         'volid' => 'local:vztmpl/ubuntu-12.04-standard_12.04-1_i386.tar.gz',
    #          'size' => 130040792
    #     }
    #  }
    #
    def templates
      data = http_action_get "nodes/#{@node}/storage/local/content"
      template_list = {}
      data.each do |ve|
        name = ve['volid'].gsub(%r{local:vztmpl\/(.*).tar.gz}, '\1')
        template_list[name] = ve
      end
      template_list
    end

    # Get CT list
    #
    # :call-seq:
    #   openvz_get -> Hash
    #
    # Return a Hash of all openvz container
    #
    # Example:
    #
    #   openvz_get
    #
    # Example return:
    #   {
    #     '101' => {
    #           'maxswap' => 536870912,
    #           'disk' => 405168128,
    #           'ip' => '192.168.1.5',
    #           'status' => 'running',
    #           'netout' => 272,
    #           'maxdisk' => 4294967296,
    #           'maxmem' => 536870912,
    #           'uptime' => 3068073,
    #           'swap' => 0,
    #           'vmid' => '101',
    #           'nproc' => '10',
    #           'diskread' => 0,
    #           'cpu' => 0.00031670581100007,
    #           'netin' => 0,
    #           'name' => 'test2.domain.com',
    #           'failcnt' => 0,
    #           'diskwrite' => 0,
    #           'mem' => 22487040,
    #           'type' => 'openvz',
    #           'cpus' => 1
    #     },
    #     [...]
    #   }
    def openvz_get
      data = http_action_get "nodes/#{@node}/openvz"
      ve_list = {}
      data.each do |ve|
        ve_list[ve['vmid']] = ve
      end
      ve_list
    end

    # Create CT container
    #
    # :call-seq:
    #   openvz_post(ostemplate, vmid) -> String
    #   openvz_post(ostemplate, vmid, options) -> String
    #
    # Return a String as task ID
    #
    # Examples:
    #
    #   openvz_post('ubuntu-10.04-standard_10.04-4_i386', 200)
    #   openvz_post('ubuntu-10.04-standard_10.04-4_i386', 200, {'hostname' => 'test.test.com', 'password' => 'testt' })
    #
    # Example return:
    #
    #   UPID:localhost:000BC66A:1279E395:521EFC4E:vzcreate:200:root@pam:
    #
    def openvz_post(ostemplate, vmid, config = {})
      config['vmid'] = vmid
      config['ostemplate'] = "local%3Avztmpl%2F#{ostemplate}.tar.gz"
      vm_definition = config.to_a.map { |v| v.join '=' }.join '&'

      http_action_post("nodes/#{@node}/openvz", vm_definition)
    end

    # Delete CT
    #
    # :call-seq:
    #   openvz_delete(vmid) -> String
    #
    # Return a string as task ID
    #
    # Example:
    #
    #   openvz_delete(200)
    #
    # Example return:
    #
    #   UPID:localhost:000BC66A:1279E395:521EFC4E:vzdelete:200:root@pam:
    #
    def openvz_delete(vmid)
      http_action_delete "nodes/#{@node}/openvz/#{vmid}"
    end

    # Get CT status
    #
    # :call-seq:
    #   openvz_delete(vmid) -> String
    #
    # Return a string as task ID
    #
    # Example:
    #
    #   openvz_delete(200)
    #
    # Example return:
    #
    #   UPID:localhost:000BC66A:1279E395:521EFC4E:vzdelete:200:root@pam:
    #
    def openvz_status(vmid)
      http_action_get "nodes/#{@node}/openvz/#{vmid}/status/current"
    end

    # Start CT
    #
    # :call-seq:
    #   openvz_start(vmid) -> String
    #
    # Return a string as task ID
    #
    # Example:
    #
    #   openvz_start(200)
    #
    # Example return:
    #
    #   UPID:localhost:000BC66A:1279E395:521EFC4E:vzstart:200:root@pam:
    #
    def openvz_start(vmid)
      http_action_post "nodes/#{@node}/openvz/#{vmid}/status/start"
    end

    # Stop CT
    #
    # :call-seq:
    #   openvz_stop(vmid) -> String
    #
    # Return a string as task ID
    #
    # Example:
    #
    #   openvz_stop(200)
    #
    # Example return:
    #
    #   UPID:localhost:000BC66A:1279E395:521EFC4E:vzstop:200:root@pam:
    #
    def openvz_stop(vmid)
      http_action_post "nodes/#{@node}/openvz/#{vmid}/status/stop"
    end

    # Shutdown CT
    #
    # :call-seq:
    #   openvz_shutdown(vmid) -> String
    #
    # Return a string as task ID
    #
    # Example:
    #
    #   openvz_shutdown(200)
    #
    # Example return:
    #
    #   UPID:localhost:000BC66A:1279E395:521EFC4E:vzshutdown:200:root@pam:
    #
    def openvz_shutdown(vmid)
      http_action_post "nodes/#{@node}/openvz/#{vmid}/status/shutdown"
    end

    # Get CT config
    #
    # :call-seq:
    #   openvz_config(vmid) -> String
    #
    # Return a string as task ID
    #
    # Example:
    #
    #   openvz_config(200)
    #
    # Example return:
    #
    #   {
    #     'quotaugidlimit' => 0,
    #     'disk' => 0,
    #     'ostemplate' => 'ubuntu-10.04-standard_10.04-4_i386.tar.gz',
    #     'hostname' => 'test.test.com',
    #     'nameserver' => '127.0.0.1 192.168.1.1',
    #     'memory' => 256,
    #     'searchdomain' => 'domain.com',
    #     'onboot' => 0,
    #     'cpuunits' => 1000,
    #     'swap' => 256,
    #     'quotatime' => 0,
    #     'digest' => 'e7e6e21a215af6b9da87a8ecb934956b8983f960',
    #     'cpus' => 1,
    #     'storage' => 'local'
    #   }
    #
    def openvz_config(vmid)
      http_action_get "nodes/#{@node}/openvz/#{vmid}/config"
    end

    # Set CT config
    #
    # :call-seq:
    #   openvz_config_set(vmid, parameters) -> Nil
    #
    # Return nil
    #
    # Example:
    #
    #   openvz_config(200, { 'swap' => 2048 })
    #
    # Example return:
    #
    #   nil
    #
    def openvz_config_set(vmid, data)
      http_action_put("nodes/#{@node}/openvz/#{vmid}/config", data)
    end

    private

    # Methods manages auth
    def create_ticket
      post_param = { username: @username, realm: @realm, password: @password }
      @site['access/ticket'].post post_param do |response, _request, _result, &_block|
        if response.code == 200
          extract_ticket response
        else
          @connection_status = 'error'
        end
      end
    end

    # Method create ticket
    def extract_ticket(response)
      data = JSON.parse(response.body)
      ticket = data['data']['ticket']
      csrf_prevention_token = data['data']['CSRFPreventionToken']
      unless ticket.nil?
        token = 'PVEAuthCookie=' + ticket.gsub!(/:/, '%3A').gsub!(/=/, '%3D')
      end
      @connection_status = 'connected'
      {
        CSRFPreventionToken: csrf_prevention_token,
        cookie: token
      }
    end

    # Extract data or return error
    def check_response(response)
      if response.code == 200
        JSON.parse(response.body)['data']
      else
        'NOK: error code = ' + response.code.to_s
      end
    end

    # Methods manage http dialogs
    def http_action_post(url, data = {})
      @site[url].post data, @auth_params do |response, _request, _result, &_block|
        check_response response
      end
    end

    def http_action_put(url, data = {})
      @site[url].put data, @auth_params do |response, _request, _result, &_block|
        check_response response
      end
    end

    def http_action_get(url, data = {})
      @site[url].get @auth_params.merge(data) do |response, _request, _result, &_block|
        check_response response
      end
    end

    def http_action_delete(url)
      @site[url].delete @auth_params do |response, _request, _result, &_block|
        check_response response
      end
    end
  end
end