require "rest_client" require "json" require "cgi" module Shelly class Client class APIError < Exception attr_reader :status_code, :body def initialize(status_code, body = {}) @status_code = status_code @body = body end def message body["message"] end def errors body["errors"] end def url body["url"] end def validation? message == "Validation Failed" end def not_found? status_code == 404 end def resource_not_found return unless not_found? message =~ /Couldn't find (.*) with/ && $1.downcase.to_sym end def unauthorized? status_code == 401 end def each_error errors.each do |field, message| yield [field.gsub('_',' ').capitalize, message].join(" ") end end end attr_reader :email, :password def initialize(email = nil, password = nil) @email = email @password = password end def api_url ENV["SHELLY_URL"] || "https://admin.winniecloud.com/apiv2" end def shellyapp_url get("/shellyapp")["url"] end def register_user(email, password, ssh_key) post("/users", :user => {:email => email, :password => password, :ssh_key => ssh_key}) end def token get("/token") end def app_configs(cloud) get("/apps/#{cloud}/configs") end def app_config(cloud, path) get("/apps/#{cloud}/configs/#{CGI.escape(path)}") end def app_create_config(cloud, path, content) post("/apps/#{cloud}/configs", :config => {:path => path, :content => content}) end def app_update_config(cloud, path, content) put("/apps/#{cloud}/configs/#{CGI.escape(path)}", :config => {:content => content}) end def app_delete_config(cloud, path) delete("/apps/#{cloud}/configs/#{CGI.escape(path)}") end def send_invitation(cloud, email) post("/apps/#{cloud}/collaborations", :email => email) end def create_app(attributes) post("/apps", :app => attributes) end def delete_app(code_name) delete("/apps/#{code_name}") end def add_ssh_key(ssh_key) post("/ssh_key", :ssh_key => ssh_key) end def start_cloud(cloud) put("/apps/#{cloud}/start") end def stop_cloud(cloud) put("/apps/#{cloud}/stop") end def apps get("/apps") end def app(code_name) get("/apps/#{code_name}") end def deploy_logs(cloud) get("/apps/#{cloud}/deploys") end def deploy_log(cloud, log) get("/apps/#{cloud}/deploys/#{log}") end def application_logs(cloud) get("/apps/#{cloud}/logs") end def database_backups(code_name) get("/apps/#{code_name}/database_backups") end def database_backup(code_name, handler) get("/apps/#{code_name}/database_backups/#{handler}") end def restore_backup(code_name, filename) put("/apps/#{code_name}/database_backups/#{filename}/restore") end def request_backup(code_name, kind = nil) post("/apps/#{code_name}/database_backups", :kind => kind) end def ssh_key_available?(ssh_key) get("/users/new", :ssh_key => ssh_key) end def app_users(cloud) get("/apps/#{cloud}/users") end def post(path, params = {}) request(path, :post, params) end def put(path, params = {}) request(path, :put, params) end def get(path, params = {}) request(path, :get, params) end def delete(path, params = {}) request(path, :delete, params) end def download_backup(cloud, filename, progress_callback = nil) File.open(filename, "w") do |out| process_response = lambda do |response| response.read_body do |chunk| out.write(chunk) progress_callback.call(chunk.size) if progress_callback end end options = request_parameters("/apps/#{cloud}/database_backups/#{filename}", :get) options = options.merge(:block_response => process_response, :headers => {:accept => "application/x-gzip"}) RestClient::Request.execute(options) end end def request(path, method, params = {}) options = request_parameters(path, method, params) RestClient::Request.execute(options) do |response, request| process_response(response) end end def headers {:accept => :json, :content_type => :json, "shelly-version" => Shelly::VERSION} end def http_basic_auth_options @email ? {:user => @email, :password => @password} : {} end def request_parameters(path, method, params = {}) {:method => method, :url => "#{api_url}#{path}", :headers => headers, :payload => params.to_json }.merge(http_basic_auth_options) end def process_response(response) body = JSON.parse(response.body) rescue JSON::ParserError && {} code = response.code raise APIError.new(code, body) if (400..599).include?(code) response.return! body end end end