# A Ruby class to call the Transcriptic REST API. You might use this if you want to # manage your Transcriptic apps from within a Ruby program, such as Capistrano. # # Example: # # require 'transcriptic' # transcriptic = Transcriptic::Client.new('me@example.com', 'mypass') # transcriptic.list_resources # transcriptic.run_info("run-625f827a") # class Transcriptic::Client include Transcriptic::UI include Transcriptic::Helpers class << self include Transcriptic::Helpers end def self.version Transcriptic::VERSION end def self.gem_version_string "transcriptic/#{version}" end attr_accessor :host, :user, :api_key def self.auth(user, password, host = Transcriptic::Auth.default_host) client = new(user, nil, host) json_decode client.post('/users/sign_in', { 'user[email]' => user, 'user[password]' => password }, :accept => 'json').to_s end def initialize(user, api_key, host = Transcriptic::Auth.default_host) @user = user @api_key = api_key @host = host end def search_resources(term, limit = 10) json_decode get("/api/resources/search.json?query=#{escape(term)}&limit=#{limit}").to_s end def list_resources json_decode get("/api/resources.json").to_s end def resource_info(id) json_decode get("/api/resources/#{id}.json").to_s end def list_runs json_decode get("/api/runs.json").to_s end def run_info(id) begin json_decode get("/api/runs/#{id}.json").to_s rescue RestClient::ResourceNotFound false end end def sign_upload(labfile, filename) json_decode get("/api/upload/sign?type=protocol-pkg&group=#{labfile.options[:group]}&name=#{labfile.options[:name]}&filename=#{filename}&version=#{labfile.options[:version]}").to_s end def upload_to_s3(path, signature) conn = Faraday.new(:url => "https://#{signature["bucket"]}.s3.amazonaws.com") do |faraday| faraday.request :multipart faraday.request :url_encoded faraday.adapter :net_http end res = conn.post "/", { "key" => signature["key"], "AWSAccessKeyId" => signature["access_key"], "acl" => "private", "success_action_status" => signature["success_action_status"], "policy" => signature["policy"], "signature" => signature["signature"], "file" => Faraday::UploadIO.new(path, 'application/jar') } end def get_protocol(labfile) begin json_decode get("/api/protocols/#{labfile.options[:name]}").to_s rescue RestClient::ResourceNotFound false end end def create_protocol(labfile) json_decode post("/api/protocols", { "protocol[name]" => labfile.options[:name], "protocol[author_email]" => labfile.options[:email], "protocol[author_name]" => labfile.options[:author], "protocol[description]" => labfile.options[:description] }).to_s end def update_protocol(labfile, signature) payload = { "protocol[name]" => labfile.options[:name], "protocol[author_email]" => labfile.options[:email], "protocol[author_name]" => labfile.options[:author], "protocol[description]" => labfile.options[:description], "protocol[package][key]" => signature["key"], "protocol[package][version]" => labfile.options[:version], "protocol[package][dependencies]" => labfile.dependencies.to_json } begin json_decode put("/api/protocols/#{labfile.options[:name]}", payload).to_s rescue RestClient::UnprocessableEntity => ex json_decode ex.response.to_s end end def create_or_get_protocol(labfile, signature) if not get_protocol(labfile) create_protocol(labfile) end update_protocol(labfile, signature) end def create_run(fd, project_id) payload = { 'zipdata' => Base64.encode64(File.open(fd).read), 'project_id' => project_id } json_decode post("/api/runs", payload, { 'Content-Type' => 'application/zip; charset=UTF-8' }).to_s end def read_logs(run_id, options=[]) query = "&" + options.join("&") unless options.empty? url = get("/api/runs/#{run_id}/logs.json?#{query}").to_s if 'not_found' == url error "Run #{run_id} not found!" end uri = URI.parse(url); http = Net::HTTP.new(uri.host, uri.port) if uri.scheme == 'https' http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE end http.read_timeout = 60 * 60 * 24 begin http.start do http.request_get(uri.path + (uri.query ? "?" + uri.query : "")) do |request| request.read_body do |chunk| yield chunk end end end rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError abort(" ! Could not connect to logging service") rescue Timeout::Error, EOFError abort("\n ! Request timed out") end end def on_warning(&blk) @warning_callback = blk end ################## def resource(uri, options={}, host=host) RestClient.proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] resource = RestClient::Resource.new(realize_full_uri(uri, host), options) resource end def get(uri, extra_headers={}) # :nodoc: process(:get, uri, extra_headers) end def post(uri, payload="", extra_headers={}, host=host) # :nodoc: process(:post, uri, extra_headers, payload, host) end def put(uri, payload, extra_headers={}) # :nodoc: process(:put, uri, extra_headers, payload) end def delete(uri, extra_headers={}) # :nodoc: process(:delete, uri, extra_headers) end def process(method, uri, extra_headers={}, payload=nil, host=host) headers = transcriptic_headers.merge(extra_headers) args = [method, payload, headers].compact resource_options = default_resource_options_for_uri(uri) begin response = resource(uri, resource_options, host).send(*args) rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError host = URI.parse(realize_full_uri(uri, host)).host Transcriptic.ui.error " ! Unable to connect to #{host}" rescue RestClient::SSLCertificateNotVerified => ex host = URI.parse(realize_full_uri(uri, host)).host Transcriptic.ui.error "WARNING: Unable to verify SSL certificate for #{host}\nTo disable SSL verification, run with TRANSCRIPTIC_SSL_VERIFY=disable" end extract_warning(response) response end def extract_warning(response) return unless response if response.headers[:x_transcriptic_warning] && @warning_callback warning = response.headers[:x_transcriptic_warning] @displayed_warnings ||= {} unless @displayed_warnings[warning] @warning_callback.call(warning) @displayed_warnings[warning] = true end end end def transcriptic_headers # :nodoc: headers = { 'X-API-Version' => '1', 'User-Agent' => self.class.gem_version_string, 'X-Ruby-Version' => RUBY_VERSION, 'X-Ruby-Platform' => RUBY_PLATFORM } headers = headers.merge({'Authorization' => "Token token=\"#{@api_key}\""}) if @api_key headers end def xml(raw) # :nodoc: REXML::Document.new(raw) end def escape(value) # :nodoc: escaped = URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) escaped.gsub('.', '%2E') # not covered by the previous URI.escape end module JSON def self.parse(json) json_decode(json) end end private def realize_full_uri(given, host=host) full_host = (host =~ /^http/) ? host : "https://www.#{host}" host = URI.parse(full_host) uri = URI.parse(given) uri.host ||= host.host uri.scheme ||= host.scheme || "https" uri.path = (uri.path[0..0] == "/") ? uri.path : "/#{uri.path}" uri.port = host.port if full_host =~ /\:\d+/ uri.to_s end def default_resource_options_for_uri(uri) if ENV["TRANSCRIPTIC_SSL_VERIFY"] == "disable" {} elsif realize_full_uri(uri) =~ %r|^https://secure.transcriptic.com| # OpenSSL::SSL::VERIFY_PEER { } #{ :verify_ssl => OpenSSL::SSL::VERIFY_NONE, :ssl_ca_file => local_ca_file } else {} end end def local_ca_file File.expand_path("../../data/cacert.pem", __FILE__) end def hash_from_xml_doc(elements) elements.inject({}) do |hash, e| next(hash) unless e.respond_to?(:children) hash.update(e.name.gsub("-","_").to_sym => case e.children.length when 0 then nil when 1 then e.text else hash_from_xml_doc(e.children) end) end end end