1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00
fog--fog/lib/fog/brightbox/compute.rb
2013-01-21 10:33:32 +00:00

490 lines
19 KiB
Ruby

require 'fog/brightbox'
require 'fog/compute'
require 'fog/brightbox/oauth2'
module Fog
module Compute
class Brightbox < Fog::Service
API_URL = "https://api.gb1.brightbox.com/"
# Client credentials
requires :brightbox_client_id, :brightbox_secret
# API endpoint settings
recognizes :brightbox_auth_url, :brightbox_api_url
# User credentials (still requires client details)
recognizes :brightbox_username, :brightbox_password, :brightbox_account
# Cached tokens
recognizes :brightbox_access_token, :brightbox_refresh_token
# Automatic token management
recognizes :brightbox_token_management
# Excon connection settings
recognizes :persistent
model_path 'fog/brightbox/models/compute'
collection :accounts
model :account
collection :applications
model :application
collection :api_clients
model :api_client
collection :servers
model :server
collection :server_groups
model :server_group
collection :firewall_policies
model :firewall_policy
collection :firewall_rules
model :firewall_rule
collection :flavors
model :flavor
collection :images
model :image
collection :load_balancers
model :load_balancer
collection :zones
model :zone
collection :cloud_ips
model :cloud_ip
collection :users
model :user
request_path 'fog/brightbox/requests/compute'
request :activate_console_server
request :add_listeners_load_balancer
request :add_nodes_load_balancer
request :add_servers_server_group
request :apply_to_firewall_policy
request :remove_firewall_policy
request :create_api_client
request :create_application
request :create_cloud_ip
request :create_firewall_policy
request :create_firewall_rule
request :create_image
request :create_load_balancer
request :create_server
request :create_server_group
request :destroy_api_client
request :destroy_application
request :destroy_cloud_ip
request :destroy_firewall_policy
request :destroy_firewall_rule
request :destroy_image
request :destroy_load_balancer
request :destroy_server
request :destroy_server_group
request :get_account
request :get_api_client
request :get_application
request :get_authenticated_user
request :get_cloud_ip
request :get_firewall_policy
request :get_firewall_rule
request :get_image
request :get_interface
request :get_load_balancer
request :get_scoped_account
request :get_server
request :get_server_group
request :get_server_type
request :get_user
request :get_zone
request :list_accounts
request :list_api_clients
request :list_applications
request :list_cloud_ips
request :list_firewall_policies
request :list_images
request :list_load_balancers
request :list_server_groups
request :list_server_types
request :list_servers
request :list_users
request :list_zones
request :map_cloud_ip
request :move_servers_server_group
request :remove_listeners_load_balancer
request :remove_nodes_load_balancer
request :remove_servers_server_group
request :reset_ftp_password_account
request :reset_ftp_password_scoped_account
request :reset_secret_api_client
request :reset_secret_application
request :shutdown_server
request :snapshot_server
request :start_server
request :stop_server
request :unmap_cloud_ip
request :update_account
request :update_api_client
request :update_application
request :update_cloud_ip
request :update_firewall_policy
request :update_firewall_rule
request :update_image
request :update_load_balancer
request :update_scoped_account
request :update_server
request :update_server_group
request :update_user
module Shared
include Fog::Brightbox::OAuth2
# Creates a new instance of the Brightbox Compute service
#
# @note If you create service using just a refresh token when it
# expires the service will no longer be able to authenticate.
#
# @param [Hash] options
# @option options [String] :brightbox_api_url Override the default (or configured) API endpoint
# @option options [String] :brightbox_auth_url Override the default (or configured) API authentication endpoint
# @option options [String] :brightbox_client_id Client identifier to authenticate with (overrides configured)
# @option options [String] :brightbox_secret Client secret to authenticate with (overrides configured)
# @option options [String] :brightbox_username Email or user identifier for user based authentication
# @option options [String] :brightbox_password Password for user based authentication
# @option options [String] :brightbox_account Account identifier to scope this connection to
# @option options [String] :connection_options Settings to pass to underlying {Fog::Connection}
# @option options [Boolean] :persistent Sets a persistent HTTP {Fog::Connection}
# @option options [String] :brightbox_access_token Sets the OAuth access token to use rather than requesting a new token
# @option options [String] :brightbox_refresh_token Sets the refresh token to use when requesting a newer access token
# @option options [String] :brightbox_token_management Overide the existing behaviour to request access tokens if expired (default is `true`)
#
def initialize(options)
# Currently authentication and api endpoints are the same but may change
@auth_url = options[:brightbox_auth_url] || Fog.credentials[:brightbox_auth_url] || API_URL
@auth_connection = Fog::Connection.new(@auth_url)
@api_url = options[:brightbox_api_url] || Fog.credentials[:brightbox_api_url] || API_URL
@connection_options = options[:connection_options] || {}
@persistent = options[:persistent] || false
@connection = Fog::Connection.new(@api_url, @persistent, @connection_options)
# Authentication options
client_id = options[:brightbox_client_id] || Fog.credentials[:brightbox_client_id]
client_secret = options[:brightbox_secret] || Fog.credentials[:brightbox_secret]
username = options[:brightbox_username] || Fog.credentials[:brightbox_username]
password = options[:brightbox_password] || Fog.credentials[:brightbox_password]
@configured_account = options[:brightbox_account] || Fog.credentials[:brightbox_account]
# Request account can be changed at anytime and changes behaviour of future requests
@scoped_account = @configured_account
credential_options = {:username => username, :password => password}
@credentials = CredentialSet.new(client_id, client_secret, credential_options)
# If existing tokens have been cached, allow continued use of them in the service
@credentials.update_tokens(options[:brightbox_access_token], options[:brightbox_refresh_token])
@token_management = options.fetch(:brightbox_token_management, true)
end
# Sets the scoped account for future requests
# @param [String] scoped_account Identifier of the account to scope request to
def scoped_account=(scoped_account)
@scoped_account = scoped_account
end
# This returns the account identifier that the request should be scoped by
# based on the options passed to the request and current configuration
#
# @param [String] options_account Any identifier passed into the request
#
# @return [String, nil] The account identifier to scope the request to or nil
def scoped_account(options_account = nil)
[options_account, @scoped_account].compact.first
end
# Resets the scoped account back to intially configured one
def scoped_account_reset
@scoped_account = @configured_account
end
# Returns the scoped account being used for requests
#
# * For API clients this is the owning account
# * For User applications this is the account specified by either +account_id+
# option on the service or the +brightbox_account+ setting in your configuration
#
# @return [Fog::Compute::Brightbox::Account]
#
def account
account_data = get_scoped_account.merge(:service => self)
Fog::Compute::Brightbox::Account.new(account_data)
end
# Returns true if authentication is being performed as a user
# @return [Boolean]
def authenticating_as_user?
@credentials.user_details?
end
# Returns true if an access token is set
# @return [Boolean]
def access_token_available?
!! @credentials.access_token
end
# Returns the current access token or nil
# @return [String,nil]
def access_token
@credentials.access_token
end
# Returns the current refresh token or nil
# @return [String,nil]
def refresh_token
@credentials.refresh_token
end
# Requests a new access token
#
# @return [String] New access token
def get_access_token
begin
get_access_token!
rescue Excon::Errors::Unauthorized, Excon::Errors::BadRequest
@credentials.update_tokens(nil, nil)
end
@credentials.access_token
end
# Requests a new access token and raises if there is a problem
#
# @return [String] New access token
# @raise [Excon::Errors::BadRequest] The credentials are expired or incorrect
#
def get_access_token!
response = request_access_token(@auth_connection, @credentials)
update_credentials_from_response(@credentials, response)
@credentials.access_token
end
# Returns an identifier for the default image for use
#
# Currently tries to find the latest version Ubuntu LTS (i686) widening
# up to the latest, official version of Ubuntu available.
#
# Highly recommended that you actually select the image you want to run
# on your servers yourself!
#
# @return [String, nil]
def default_image
return @default_image_id unless @default_image_id.nil?
@default_image_id = Fog.credentials[:brightbox_default_image] || select_default_image
end
private
# This makes a request of the API based on the configured setting for
# token management.
#
# @param [Hash] options Excon compatible options
# @see https://github.com/geemus/excon/blob/master/lib/excon/connection.rb
#
# @return [Hash] Data of response body
#
def make_request(options)
if @token_management
managed_token_request(options)
else
authenticated_request(options)
end
end
# This request checks for access tokens and will ask for a new one if
# it receives Unauthorized from the API before repeating the request
#
# @param [Hash] options Excon compatible options
#
# @return [Excon::Response]
def managed_token_request(options)
begin
get_access_token unless access_token_available?
response = authenticated_request(options)
rescue Excon::Errors::Unauthorized
get_access_token
response = authenticated_request(options)
end
end
# This request makes an authenticated request of the API using currently
# setup credentials.
#
# @param [Hash] options Excon compatible options
#
# @see https://github.com/geemus/excon/blob/master/lib/excon/connection.rb
#
# @return [Excon::Response]
def authenticated_request(options)
headers = options[:headers] || {}
headers.merge!("Authorization" => "OAuth #{@credentials.access_token}", "Content-Type" => "application/json")
options[:headers] = headers
# TODO This is just a wrapper around a call to Excon::Connection#request
# so can be extracted from Compute by passing in the connection,
# credentials and options
@connection.request(options)
end
end
# The Mock Service allows you to run a fake instance of the Service
# which makes no real connections.
#
# @todo Implement
#
class Mock
include Shared
def request(method, path, expected_responses, parameters = {})
_request
end
def request_access_token(connection, credentials)
_request
end
private
def _request
raise Fog::Errors::MockNotImplemented
end
def select_default_image
"img-mockd"
end
end
# The Real Service actually makes real connections to the Brightbox
# service.
#
class Real
include Shared
# Makes an API request to the given path using passed options or those
# set with the service setup
#
# @todo Standard Fog behaviour is to return the Excon::Response but
# this was unintentionally changed to be the Hash version of the
# data in the body. This loses access to some details and should
# be corrected in a backwards compatible manner
#
# @overload request(params)
# @param [Hash] params Excon compatible options
# @option params [String] :body text to be sent over a socket
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request
# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
# @option params [String] :path appears after 'scheme://host:port/'
# @option params [Fixnum] :port The port on which to connect, to the destination host
# @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
# @return [Excon::Response]
# @see https://github.com/geemus/excon/blob/master/lib/excon/connection.rb
#
# @overload request(method, path, expected_responses, params = {})
# @param [String] method HTTP method to use for the request
# @param [String] path The absolute path for the request
# @param [Array<Fixnum>] expected_responses HTTP response codes that have been successful
# @param [Hash] params Keys and values for JSON
# @option params [String] :account_id The scoping account if required
# @deprecated #request with multiple arguments is deprecated
# since it is inconsistent with original fog version.
# @return [Hash]
def request(*args)
if args.size == 1
authenticated_request(*args)
else
Fog::Logger.deprecation("#request with multiple parameters is deprecated, use #wrapped_request instead [light_black](#{caller.first})[/]")
wrapped_request(*args)
end
end
# Makes a request but with seperated arguments and parses the response to a hash
#
# @note #wrapped_request is the non-standard form of request introduced by mistake
#
# @param [String] method HTTP method to use for the request
# @param [String] path The absolute path for the request
# @param [Array<Fixnum>] expected_responses HTTP response codes that have been successful
# @param [Hash] parameters Keys and values for JSON
# @option parameters [String] :account_id The scoping account if required
#
# @return [Hash]
def wrapped_request(method, path, expected_responses, parameters = {})
_wrapped_request(method, path, expected_responses, parameters)
end
private
# Wrapped request is the non-standard form of request introduced by mistake
#
# @param [String] method HTTP method to use for the request
# @param [String] path The absolute path for the request
# @param [Array<Fixnum>] expected_responses HTTP response codes that have been successful
# @param [Hash] parameters Keys and values for JSON
# @option parameters [String] :account_id The scoping account if required
#
# @return [Hash]
def _wrapped_request(method, path, expected_responses, parameters = {})
request_options = {
:method => method.to_s.upcase,
:path => path,
:expects => expected_responses
}
# Select the account to scope for this request
account = scoped_account(parameters.fetch(:account_id, nil))
if account
request_options[:query] = { :account_id => account }
end
request_options[:body] = Fog::JSON.encode(parameters) unless parameters.empty?
response = make_request(request_options)
# FIXME We should revert to returning the Excon::Request after a suitable
# configuration option is in place to switch back to this incorrect behaviour
unless response.body.empty?
Fog::JSON.decode(response.body)
else
response
end
end
# Queries the API and tries to select the most suitable official Image
# to use if the user chooses not to select their own.
def select_default_image
return @default_image_id unless @default_image_id.nil?
all_images = list_images
official_images = all_images.select {|img| img["official"] == true}
ubuntu_lts_images = official_images.select {|img| img["name"] =~ /Ubuntu.*LTS/}
ubuntu_lts_i686_images = ubuntu_lts_images.select {|img| img["arch"] == "i686"}
if ubuntu_lts_i686_images.empty?
# Accept other architectures
if ubuntu_lts_images.empty?
# Accept non-LTS versions of Ubuntu
unsorted_images = official_images.select {|img| img["name"] =~ /Ubuntu/}
else
unsorted_images = ubuntu_lts_images
end
else
unsorted_images = ubuntu_lts_i686_images
end
# Get the latest and use it's ID for the default image
@default_image_id = unsorted_images.sort {|a,b| a["created_at"] <=> b["created_at"]}.first["id"]
rescue
nil
end
end
end
end
end