mod 'nanliu/staging', '1.0.3'
mod 'stackforge/openstacklib', '5.1.0'
+mod 'aimonb/aviator', '0.5.1'
--- /dev/null
+![Aviator](https://raw.github.com/aviator/www/gh-pages/images/logo-small.png)
+<br/>A lightweight library for communicating with the OpenStack API.
+
+[![Build Status](https://travis-ci.org/aviator/aviator.png?branch=master)](https://travis-ci.org/aviator/aviator)
+[![Coverage Status](https://coveralls.io/repos/aviator/aviator/badge.png?branch=master)](https://coveralls.io/r/aviator/aviator?branch=master)
+[![Code Climate](https://codeclimate.com/github/aviator/aviator.png)](https://codeclimate.com/github/aviator/aviator)
+[![Gem Version](https://badge.fury.io/rb/aviator.png)](http://badge.fury.io/rb/aviator)
+[![Dependency Status](https://gemnasium.com/aviator/aviator.png)](https://gemnasium.com/aviator/aviator)
+
+
+<a href="http://aviator.github.io/www/">Usage and Installation</a>
--- /dev/null
+# A sample Gemfile
+source "https://rubygems.org"
+
+gem 'ci_reporter'
+gem 'simplecov'
+gem 'simplecov-rcov'
+gem "rspec", '~> 2.0'
+gem "mocha"
+gem 'puppet', '= 3.5.1'
+gem 'puppet-lint'
+gem 'facter', '>= 1.6.10'
+gem 'rspec-puppet', :git => 'https://github.com/rodjek/rspec-puppet.git', :branch => 'master'
+gem 'rake', '>= 0.9.2'
+gem 'puppetlabs_spec_helper', '0.3.0'
+gem 'test-unit'
+gem 'rspec_junit_formatter'
+gem 'rspec-puppet-utils'
--- /dev/null
+GIT
+ remote: https://github.com/rodjek/rspec-puppet.git
+ revision: 389f99ef666521fec1b4530fe69dc1ab84a060a8
+ branch: master
+ specs:
+ rspec-puppet (1.0.1)
+ rspec
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (2.2.8)
+ builder (3.2.2)
+ ci_reporter (2.0.0)
+ builder (>= 2.1.2)
+ diff-lcs (1.2.5)
+ docile (1.1.5)
+ facter (2.2.0)
+ CFPropertyList (~> 2.2.6)
+ hiera (1.3.4)
+ json_pure
+ json_pure (1.8.1)
+ metaclass (0.0.4)
+ mocha (1.1.0)
+ metaclass (~> 0.0.1)
+ multi_json (1.10.1)
+ power_assert (0.1.3)
+ puppet (3.5.1)
+ facter (> 1.6, < 3)
+ hiera (~> 1.0)
+ json_pure
+ rgen (~> 0.6.5)
+ puppet-lint (1.0.1)
+ puppetlabs_spec_helper (0.3.0)
+ mocha (>= 0.10.5)
+ rake
+ rspec (>= 2.9.0)
+ rspec-puppet (>= 0.1.1)
+ rake (10.3.2)
+ rgen (0.6.6)
+ rspec (2.99.0)
+ rspec-core (~> 2.99.0)
+ rspec-expectations (~> 2.99.0)
+ rspec-mocks (~> 2.99.0)
+ rspec-core (2.99.2)
+ rspec-expectations (2.99.2)
+ diff-lcs (>= 1.1.3, < 2.0)
+ rspec-mocks (2.99.2)
+ rspec-puppet-utils (2.0.4)
+ rspec_junit_formatter (0.2.0)
+ builder (< 4)
+ rspec (>= 2, < 4)
+ rspec-core (!= 2.12.0)
+ simplecov (0.9.0)
+ docile (~> 1.1.0)
+ multi_json
+ simplecov-html (~> 0.8.0)
+ simplecov-html (0.8.0)
+ simplecov-rcov (0.2.3)
+ simplecov (>= 0.4.1)
+ test-unit (3.0.1)
+ power_assert
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ ci_reporter
+ facter (>= 1.6.10)
+ mocha
+ puppet (= 3.5.1)
+ puppet-lint
+ puppetlabs_spec_helper (= 0.3.0)
+ rake (>= 0.9.2)
+ rspec (~> 2.0)
+ rspec-puppet!
+ rspec-puppet-utils
+ rspec_junit_formatter
+ simplecov
+ simplecov-rcov
+ test-unit
--- /dev/null
+Copyright (c) 2014 Mark Maglana
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+name 'aimonb-aviator'
+version '0.5.1'
+source 'https://github.com/aimonb/puppet_aviator'
+author 'aimonb'
+license 'MIT License'
+summary 'Puppet feature wrapper for the Aviator OpenStack API library for Ruby'
+description 'UNKNOWN'
+project_page 'https://github.com/aimonb/puppet_aviator'
+
+## Add dependencies, if any:
+# dependency 'username/name', '>= 1.2.0'
--- /dev/null
+Puppet Aviator
+
+A feature module for the Aviator project.
+
+Aviator is a lightweight library for communicating with the OpenStack
+API
+
+See Aviator_README.md for more information on Aviator.
+
+License
+-------
+MIT License
+
+Contact
+-------
+Aimon Bustardo <me at aimon dot net>
+
+Example Usage:
+-------
+
+ $LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..',
+'..'))
+ require 'puppet/feature/aviator'
+
+ configuration = {
+ :provider => 'openstack',
+ :auth_service => {
+ :name => 'identity',
+ :host_uri => 'http://devstack:5000/v2.0',
+ :request => 'create_token',
+ :validator => 'list_tenants'
+ },
+ :auth_credentials => {
+ :username => 'myusername',
+ :password => 'mypassword',
+ :tenant_name => 'myproject'
+ }
+ }
+
+ openstack = Aviator::Session.new(:config => configuration)
+
+ openstack.authenticate
+ response = openstack.request :identity_service, :list_tenants, :endpoint_type => 'admin'
+
+ puts response[:body]
--- /dev/null
+{
+ "Aviator_README.md": "06fac4cf003223c93ed23ea30e134e0a",
+ "Gemfile": "6d7f95a9e9993f49da52c1aa034ca599",
+ "Gemfile.lock": "5f2229c686025c1c5fcf0386bf08ebc1",
+ "LICENSE.txt": "25c9cebc4248703a08cfe5c333398bc6",
+ "Modulefile": "887af1b027892ea3a1e45350d9734643",
+ "README": "29e71fa36fbf6b393b3922ee892a9cc3",
+ "feature/aviator/compatibility.rb": "d103a261221e127c48ae8db01623cbb7",
+ "feature/aviator/core/cli/describer.rb": "a94adec4d61b706461597d23df66f76f",
+ "feature/aviator/core/cli.rb": "4fe815c3d85859dd6a9893a5aa38c7f8",
+ "feature/aviator/core/logger.rb": "99aa5ef113f491a563fdf8ecf6a44db2",
+ "feature/aviator/core/request.rb": "2477263dfc7b32751906c39448600838",
+ "feature/aviator/core/request_builder.rb": "2e3996fcfe619898c2b4638c96cc1616",
+ "feature/aviator/core/response.rb": "71629a33c4778a79b9f27fb4b8d469c0",
+ "feature/aviator/core/service.rb": "8f3c0b07e8a0c2d4ef6af7c7e0113d85",
+ "feature/aviator/core/session.rb": "3dabc2f13471632e809eb6ace4b6fbbe",
+ "feature/aviator/core.rb": "3d116a1ade58c869219ad2c2e3a01e59",
+ "feature/aviator/hashish.rb": "b9cf59c89b22cb6b39ec6916db1c5fb6",
+ "feature/aviator/openstack/common/v2/admin/base.rb": "8e88c76b3aa87bf3f69772bc5a7b4cfa",
+ "feature/aviator/openstack/common/v2/public/base.rb": "e1688afd9f65822599cfc8996256f979",
+ "feature/aviator/openstack/compute/v2/admin/confirm_server_resize.rb": "d7c43043c66a862d5232e999254c8f1a",
+ "feature/aviator/openstack/compute/v2/admin/create_network.rb": "b11ff615a495b3189ff18d2fdd580340",
+ "feature/aviator/openstack/compute/v2/admin/get_host_details.rb": "4e0200122c40d2acd95ca29ffb1a648f",
+ "feature/aviator/openstack/compute/v2/admin/list_hosts.rb": "25448fc1b14189d0d6aa6562c25d2172",
+ "feature/aviator/openstack/compute/v2/admin/lock_server.rb": "ff124f629375a069d0a39674a835669d",
+ "feature/aviator/openstack/compute/v2/admin/migrate_server.rb": "9b27fe512de6592e28e8ae534ef4e227",
+ "feature/aviator/openstack/compute/v2/admin/reset_server.rb": "83ceb5bde8297f40637901107c0779ce",
+ "feature/aviator/openstack/compute/v2/admin/resize_server.rb": "38d073f289de8cbcd54326dfcceb1203",
+ "feature/aviator/openstack/compute/v2/admin/revert_server_resize.rb": "c86f20c4c92417da7470a93adf2ac886",
+ "feature/aviator/openstack/compute/v2/admin/unlock_server.rb": "844f5380140805475c7a44d964960558",
+ "feature/aviator/openstack/compute/v2/public/change_admin_password.rb": "e5ed804402e4c969de89f669bbdbed52",
+ "feature/aviator/openstack/compute/v2/public/create_image.rb": "605c7f1dccc0e25f75a22b8133e0ac1c",
+ "feature/aviator/openstack/compute/v2/public/create_server.rb": "3d1408639e56cf9e32d26967dbbfc3f3",
+ "feature/aviator/openstack/compute/v2/public/delete_image.rb": "fb0f14aa4d3cd80e0b32ebcd0d32b62b",
+ "feature/aviator/openstack/compute/v2/public/delete_image_metadata_item.rb": "8f24549420f5b2ec73d245d84364f6a1",
+ "feature/aviator/openstack/compute/v2/public/delete_server.rb": "89fca14a34d08483992b03e0b3256e24",
+ "feature/aviator/openstack/compute/v2/public/delete_server_metadata_item.rb": "1caa8f632ee0e7964d0774f8fcdde5f8",
+ "feature/aviator/openstack/compute/v2/public/get_flavor_details.rb": "335012feb1abc4a0c0bfd57e09dd906f",
+ "feature/aviator/openstack/compute/v2/public/get_image_details.rb": "779845403f9314ddbda05121b54cd428",
+ "feature/aviator/openstack/compute/v2/public/get_image_metadata_item.rb": "cdc2caa68f52f33f5944770cb0594623",
+ "feature/aviator/openstack/compute/v2/public/get_network_details.rb": "43f08bb5513fd78f5685f065111a89e7",
+ "feature/aviator/openstack/compute/v2/public/get_server.rb": "9b764bb9b93cf368b7bec8ed70654490",
+ "feature/aviator/openstack/compute/v2/public/get_server_metadata_item.rb": "7e6a0feca6e6e2afc89e8b8cf00e6493",
+ "feature/aviator/openstack/compute/v2/public/list_addresses.rb": "ff343fead191c3d13677448daee3822c",
+ "feature/aviator/openstack/compute/v2/public/list_flavors.rb": "7050181d84ada85de9a3cfe476040432",
+ "feature/aviator/openstack/compute/v2/public/list_image_metadata.rb": "36cc324f2f8ec4e12a7f47846099aaa1",
+ "feature/aviator/openstack/compute/v2/public/list_images.rb": "9dcace5a72ef644072ede3bdcdf031ed",
+ "feature/aviator/openstack/compute/v2/public/list_networks.rb": "5639d0a86c7b37a0ba957f9cdb5a83ab",
+ "feature/aviator/openstack/compute/v2/public/list_server_metadata.rb": "5815bd0f48e81dea65c13bb0bb4c944e",
+ "feature/aviator/openstack/compute/v2/public/list_servers.rb": "deaf400faab4a9c0dd632be988fa002b",
+ "feature/aviator/openstack/compute/v2/public/pause_server.rb": "e2ac1913b925f61707516103d5c204cf",
+ "feature/aviator/openstack/compute/v2/public/reboot_server.rb": "73a34c9cf36b087e167e4d5a0a0b9762",
+ "feature/aviator/openstack/compute/v2/public/rebuild_server.rb": "907eee7ab63f3c8c3c93c9cc522aa208",
+ "feature/aviator/openstack/compute/v2/public/resume_server.rb": "f24ef2b6d7e4be1664d89a5d1bd76c2f",
+ "feature/aviator/openstack/compute/v2/public/root.rb": "cee3dd2aff0710d0f5fccc23508a7f58",
+ "feature/aviator/openstack/compute/v2/public/set_image_metadata.rb": "e49c35f9b95a268803578549ffce5cc0",
+ "feature/aviator/openstack/compute/v2/public/set_server_metadata.rb": "ece9d9a873ef6a3e468ae7285cd335f1",
+ "feature/aviator/openstack/compute/v2/public/suspend_server.rb": "a9fe5dc5c43dbb9da293de1eecd440a4",
+ "feature/aviator/openstack/compute/v2/public/unpause_server.rb": "9075ce7b123abce8917371fc545bbf92",
+ "feature/aviator/openstack/compute/v2/public/update_image_metadata.rb": "453dc71ce53a784322460afe8f8a0ffd",
+ "feature/aviator/openstack/compute/v2/public/update_server.rb": "19bab4a3b3b5fc8af215c53f3582f0cc",
+ "feature/aviator/openstack/compute/v2/public/update_server_metadata.rb": "72107bf121c284fb859e1d0d544c9f8c",
+ "feature/aviator/openstack/identity/v2/admin/add_role_to_user_on_tenant.rb": "d5962f765a8e2091aa0d7feab0f860b6",
+ "feature/aviator/openstack/identity/v2/admin/create_tenant.rb": "eb5b538285459bbc70da43ecb10a2ae1",
+ "feature/aviator/openstack/identity/v2/admin/create_user.rb": "a6f1152f0d2f2db837e9c87bf6dc8c76",
+ "feature/aviator/openstack/identity/v2/admin/delete_role_from_user_on_tenant.rb": "82e5b5520e9e10e8dba716b2e852cad9",
+ "feature/aviator/openstack/identity/v2/admin/delete_tenant.rb": "ad0bd04e86d4025a462ef6094313db63",
+ "feature/aviator/openstack/identity/v2/admin/delete_user.rb": "152712c94b6cec9da57cbff82a983ae1",
+ "feature/aviator/openstack/identity/v2/admin/get_tenant_by_id.rb": "0c93905f14e07e67f709739dd763d6e1",
+ "feature/aviator/openstack/identity/v2/admin/list_tenants.rb": "e75b66b966e49f045b7c61789ffd5b5e",
+ "feature/aviator/openstack/identity/v2/admin/list_users.rb": "afe6aea93af4ce623eb423e42a56473f",
+ "feature/aviator/openstack/identity/v2/admin/update_tenant.rb": "5e0250596b90187e09f5da7b2bb6c283",
+ "feature/aviator/openstack/identity/v2/admin/update_user.rb": "dd0ade2d22271f2e236caa42c65fb2bf",
+ "feature/aviator/openstack/identity/v2/public/create_token.rb": "2e44dafa7758d4610a5c06d54fc53ffa",
+ "feature/aviator/openstack/identity/v2/public/list_tenants.rb": "826d93b39fba2b8e85c4c9979f363afd",
+ "feature/aviator/openstack/identity/v2/public/root.rb": "49c81b24643287440458224c9eabc350",
+ "feature/aviator/openstack/image/v1/public/list_public_images.rb": "746ac90e4d5099a64ad08f4cc3829d97",
+ "feature/aviator/openstack/image/v1/public/root.rb": "b3b0897ea44361584961832fedf8f2ce",
+ "feature/aviator/openstack/metering/v1/admin/list_projects.rb": "22fc778d98d8424efcd14fa64e023c91",
+ "feature/aviator/openstack/volume/v1/public/create_volume.rb": "b0118b8f421376da64eb4a93afc483ec",
+ "feature/aviator/openstack/volume/v1/public/delete_volume.rb": "ebbcd8a8e470259cabed51fe41a03249",
+ "feature/aviator/openstack/volume/v1/public/get_volume.rb": "9a977461b76e1846e256d41bc5636c92",
+ "feature/aviator/openstack/volume/v1/public/list_volume_types.rb": "4338eb77f2e6df58a57fcd6cf22491cd",
+ "feature/aviator/openstack/volume/v1/public/list_volumes.rb": "2930615edc76b89b9e5888ca58d4ddd7",
+ "feature/aviator/openstack/volume/v1/public/root.rb": "fd7f423b9080b158b341530ff37b868e",
+ "feature/aviator/openstack/volume/v1/public/update_volume.rb": "67b9d740d4477d2d1cb22fa931f91458",
+ "feature/aviator/string.rb": "3c412951f02b4268890b599125174360",
+ "feature/aviator/version.rb": "10852d0b210481f820997ca1948953ab",
+ "feature/aviator.rb": "3ce701ef95c1be09eb46bcc12eeaac32",
+ "feature/composite_io.rb": "7578e6fc78d81b363658d0c047ef7355",
+ "feature/faraday/adapter/em_http.rb": "2b62006966ab41b68ba4b22f7499c6e7",
+ "feature/faraday/adapter/em_http_ssl_patch.rb": "353bac212b9c67a415cad9c2d0a33a1f",
+ "feature/faraday/adapter/em_synchrony/parallel_manager.rb": "ed23bb89721bfcc7be531d8a7fa1d75a",
+ "feature/faraday/adapter/em_synchrony.rb": "4d9eb3f1ca759f7008c4cd51ad055ddc",
+ "feature/faraday/adapter/excon.rb": "4d1bcdeeeb3623c5f5847350420102cb",
+ "feature/faraday/adapter/httpclient.rb": "cffdad2f5f9f109fb62ace392fabbcf1",
+ "feature/faraday/adapter/net_http.rb": "98ca2bafc840b0049e12448c12f7d9f6",
+ "feature/faraday/adapter/net_http_persistent.rb": "1d8cbd07de4b3464ed42774728d866d8",
+ "feature/faraday/adapter/patron.rb": "771a78d359202538a062b6e1de186b62",
+ "feature/faraday/adapter/rack.rb": "70078f81411a4294bd20302f9a857120",
+ "feature/faraday/adapter/test.rb": "e3177c396bb40d922e8aee81c745b173",
+ "feature/faraday/adapter/typhoeus.rb": "2bb446cc26a8fce211a92670838e977c",
+ "feature/faraday/adapter.rb": "c8e0aacec14e78a9d1b73bc674c147d7",
+ "feature/faraday/autoload.rb": "c3825f673dcd897eccbbf0204b290342",
+ "feature/faraday/connection.rb": "0ffff1f6f1996dbb7643c87e5acc482d",
+ "feature/faraday/error.rb": "a5900e607b1573bb6b6549e6573e2e90",
+ "feature/faraday/middleware.rb": "da7f0af70a005cabe297f64f613e04f5",
+ "feature/faraday/options.rb": "d92eea6cceda8cd61a984893ba5cc25c",
+ "feature/faraday/parameters.rb": "162fbb7a45756695909c49e099d6b412",
+ "feature/faraday/rack_builder.rb": "e1a93cd64e1d555530b6aa31b5b7812c",
+ "feature/faraday/request/authorization.rb": "a2aa3b3b22fa39e4f1fc6c87008a5e50",
+ "feature/faraday/request/basic_authentication.rb": "3da9b3b931d19e9669c3b32841ececfd",
+ "feature/faraday/request/instrumentation.rb": "c48435dbd1ba855f71c4f538baa7b665",
+ "feature/faraday/request/multipart.rb": "dcc172d9443f53d28d06cba93de2cd7b",
+ "feature/faraday/request/retry.rb": "57892cf880e6db7486daf8e015e9418a",
+ "feature/faraday/request/token_authentication.rb": "aedd602972c21fccb6b672d8caaa77ac",
+ "feature/faraday/request/url_encoded.rb": "7d8715e5cea35a1d3416a174e1fb492e",
+ "feature/faraday/request.rb": "0d5064fe1ae944b7aa21a111ccfbaf4d",
+ "feature/faraday/response/logger.rb": "0d4a6a3809bb3612715faf2b862ed147",
+ "feature/faraday/response/raise_error.rb": "8544053fd9007b42885793222f917b26",
+ "feature/faraday/response.rb": "6423348722701307e11f261b4ca09af8",
+ "feature/faraday/upload_io.rb": "7b0b80bc2cbbfca411a867d621f7b497",
+ "feature/faraday/utils.rb": "4df493a45499dbca0770f613da3d7bf6",
+ "feature/faraday.rb": "61f81f20888a0a1c992c0b108c96019d",
+ "feature/multipart_post.rb": "3433edf755f20e56edf3c167492a8a0f",
+ "feature/multipartable.rb": "5465ba7b40057b35eccf93bd1e50180d",
+ "feature/net/http/post/multipart.rb": "469d05036cd0b981b201d0fe50360f5b",
+ "feature/parts.rb": "c6a86930e784e4ab7d8d022b6f64e636",
+ "lib/puppet/feature/aviator/core/cli/describer.rb": "6cadcef2c8c51b5665243f090151afc6",
+ "lib/puppet/feature/aviator/core/cli.rb": "4fe815c3d85859dd6a9893a5aa38c7f8",
+ "lib/puppet/feature/aviator/core/logger.rb": "99aa5ef113f491a563fdf8ecf6a44db2",
+ "lib/puppet/feature/aviator/core/request.rb": "2477263dfc7b32751906c39448600838",
+ "lib/puppet/feature/aviator/core/request_builder.rb": "1705d21fb6db847d6521f643047f8675",
+ "lib/puppet/feature/aviator/core/response.rb": "2c6d777c3bf886a36e09855ec96d3911",
+ "lib/puppet/feature/aviator/core/service.rb": "3186cbe84f209286f03b8c8a347c38cb",
+ "lib/puppet/feature/aviator/core/session.rb": "a82ca88a3f76379184481e9d9d88bb5c",
+ "lib/puppet/feature/aviator/core/utils/compatibility.rb": "d103a261221e127c48ae8db01623cbb7",
+ "lib/puppet/feature/aviator/core/utils/hashish.rb": "3e54d1cdb0b25379c4adb73057072fbb",
+ "lib/puppet/feature/aviator/core/utils/string.rb": "3b1939aab12529f545b8a10ab27c0e05",
+ "lib/puppet/feature/aviator/core.rb": "8fd9c5413074f0de2aa5c5860abbbef8",
+ "lib/puppet/feature/aviator/openstack/common/requests/v0/public/base.rb": "c9690b0fa25efdcdbf6184a740a84fa3",
+ "lib/puppet/feature/aviator/openstack/common/requests/v2/admin/base.rb": "8e88c76b3aa87bf3f69772bc5a7b4cfa",
+ "lib/puppet/feature/aviator/openstack/common/requests/v2/public/base.rb": "4766de221d8e84c07e5bc997f4b4bda5",
+ "lib/puppet/feature/aviator/openstack/common/requests/v3/public/base.rb": "ac958d139dc2eff20ec08e591109bb42",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/confirm_server_resize.rb": "d7c43043c66a862d5232e999254c8f1a",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/create_network.rb": "b11ff615a495b3189ff18d2fdd580340",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/get_host_details.rb": "4e0200122c40d2acd95ca29ffb1a648f",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/list_hosts.rb": "25448fc1b14189d0d6aa6562c25d2172",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/list_hypervisors.rb": "d531290063e073bc72a0f508e5946989",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/lock_server.rb": "ff124f629375a069d0a39674a835669d",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/migrate_server.rb": "9b27fe512de6592e28e8ae534ef4e227",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/reset_server.rb": "83ceb5bde8297f40637901107c0779ce",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/resize_server.rb": "38d073f289de8cbcd54326dfcceb1203",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/revert_server_resize.rb": "ec60b35f97bb6de54d06ae53ab0a48f3",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/admin/unlock_server.rb": "844f5380140805475c7a44d964960558",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/add_floating_ip.rb": "5ce446270f6216b19922f62a63e983bc",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/allocate_floating_ip.rb": "be0c5b2e87a7bf8d29b5997240aca6e5",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/change_admin_password.rb": "e5ed804402e4c969de89f669bbdbed52",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/create_image.rb": "605c7f1dccc0e25f75a22b8133e0ac1c",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/create_keypair.rb": "626715aa5a067328afa056229ee6bc96",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/create_server.rb": "b58dc498308a031f23c8ba2d6cf49293",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/delete_image.rb": "fb0f14aa4d3cd80e0b32ebcd0d32b62b",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/delete_image_metadata_item.rb": "8f24549420f5b2ec73d245d84364f6a1",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/delete_server.rb": "89fca14a34d08483992b03e0b3256e24",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/delete_server_metadata_item.rb": "1caa8f632ee0e7964d0774f8fcdde5f8",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/get_flavor_details.rb": "335012feb1abc4a0c0bfd57e09dd906f",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/get_image_details.rb": "779845403f9314ddbda05121b54cd428",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/get_image_metadata_item.rb": "cdc2caa68f52f33f5944770cb0594623",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/get_network_details.rb": "43f08bb5513fd78f5685f065111a89e7",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/get_server.rb": "9b764bb9b93cf368b7bec8ed70654490",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/get_server_metadata_item.rb": "7e6a0feca6e6e2afc89e8b8cf00e6493",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_addresses.rb": "ff343fead191c3d13677448daee3822c",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_flavors.rb": "7050181d84ada85de9a3cfe476040432",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_floating_ips.rb": "c967d5705e55c1840f1ff18d8b198936",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_image_metadata.rb": "36cc324f2f8ec4e12a7f47846099aaa1",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_images.rb": "9dcace5a72ef644072ede3bdcdf031ed",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_keypairs.rb": "7f2fc592742874b8a9cd05bd67eb8fad",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_networks.rb": "5639d0a86c7b37a0ba957f9cdb5a83ab",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_server_metadata.rb": "5815bd0f48e81dea65c13bb0bb4c944e",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/list_servers.rb": "deaf400faab4a9c0dd632be988fa002b",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/pause_server.rb": "e2ac1913b925f61707516103d5c204cf",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/reboot_server.rb": "73a34c9cf36b087e167e4d5a0a0b9762",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/rebuild_server.rb": "907eee7ab63f3c8c3c93c9cc522aa208",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/resume_server.rb": "f24ef2b6d7e4be1664d89a5d1bd76c2f",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/root.rb": "cee3dd2aff0710d0f5fccc23508a7f58",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/set_image_metadata.rb": "e49c35f9b95a268803578549ffce5cc0",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/set_server_metadata.rb": "ece9d9a873ef6a3e468ae7285cd335f1",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/suspend_server.rb": "a9fe5dc5c43dbb9da293de1eecd440a4",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/unpause_server.rb": "9075ce7b123abce8917371fc545bbf92",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/update_image_metadata.rb": "453dc71ce53a784322460afe8f8a0ffd",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/update_server.rb": "19bab4a3b3b5fc8af215c53f3582f0cc",
+ "lib/puppet/feature/aviator/openstack/compute/requests/v2/public/update_server_metadata.rb": "72107bf121c284fb859e1d0d544c9f8c",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/add_role_to_user_on_tenant.rb": "d5962f765a8e2091aa0d7feab0f860b6",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/create_tenant.rb": "eb5b538285459bbc70da43ecb10a2ae1",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/create_user.rb": "817e3936db1e0a000014b0464a64a8ea",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/delete_role_from_user_on_tenant.rb": "82e5b5520e9e10e8dba716b2e852cad9",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/delete_tenant.rb": "ad0bd04e86d4025a462ef6094313db63",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/delete_user.rb": "1a727d8adc5d4a1753acd7224f77e505",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/get_tenant_by_id.rb": "0c93905f14e07e67f709739dd763d6e1",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/get_user.rb": "3fb7869597c8f68adb91fefe5d0c0791",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/list_tenants.rb": "e75b66b966e49f045b7c61789ffd5b5e",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/list_users.rb": "058de058e0a98e2a7549a52e384b5807",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/update_tenant.rb": "ff218d457b3ddce85168a85e98014c51",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/admin/update_user.rb": "47e295d5f138acda2bcb8b6a5ee1f87d",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/public/create_token.rb": "2e44dafa7758d4610a5c06d54fc53ffa",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/public/list_tenants.rb": "826d93b39fba2b8e85c4c9979f363afd",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v2/public/root.rb": "49c81b24643287440458224c9eabc350",
+ "lib/puppet/feature/aviator/openstack/identity/requests/v3/public/create_token.rb": "c092532638e57c55c38eaae925e41b71",
+ "lib/puppet/feature/aviator/openstack/image/requests/v1/public/list_public_images.rb": "746ac90e4d5099a64ad08f4cc3829d97",
+ "lib/puppet/feature/aviator/openstack/image/requests/v1/public/root.rb": "b3b0897ea44361584961832fedf8f2ce",
+ "lib/puppet/feature/aviator/openstack/metering/requests/v1/admin/list_projects.rb": "22fc778d98d8424efcd14fa64e023c91",
+ "lib/puppet/feature/aviator/openstack/provider.rb": "e9ba93565ad683778c5901f30ab9fead",
+ "lib/puppet/feature/aviator/openstack/volume/requests/v1/public/create_volume.rb": "b0118b8f421376da64eb4a93afc483ec",
+ "lib/puppet/feature/aviator/openstack/volume/requests/v1/public/delete_volume.rb": "ebbcd8a8e470259cabed51fe41a03249",
+ "lib/puppet/feature/aviator/openstack/volume/requests/v1/public/get_volume.rb": "9a977461b76e1846e256d41bc5636c92",
+ "lib/puppet/feature/aviator/openstack/volume/requests/v1/public/list_volume_types.rb": "4338eb77f2e6df58a57fcd6cf22491cd",
+ "lib/puppet/feature/aviator/openstack/volume/requests/v1/public/list_volumes.rb": "2930615edc76b89b9e5888ca58d4ddd7",
+ "lib/puppet/feature/aviator/openstack/volume/requests/v1/public/root.rb": "fd7f423b9080b158b341530ff37b868e",
+ "lib/puppet/feature/aviator/openstack/volume/requests/v1/public/update_volume.rb": "67b9d740d4477d2d1cb22fa931f91458",
+ "lib/puppet/feature/aviator/version.rb": "7dacc5a489888d3bbb3e29d0d3fd0857",
+ "lib/puppet/feature/aviator.rb": "2bfe991671863c6b9f66fefb45edd586",
+ "lib/puppet/feature/composite_io.rb": "7578e6fc78d81b363658d0c047ef7355",
+ "lib/puppet/feature/faraday/adapter/em_http.rb": "2b62006966ab41b68ba4b22f7499c6e7",
+ "lib/puppet/feature/faraday/adapter/em_http_ssl_patch.rb": "353bac212b9c67a415cad9c2d0a33a1f",
+ "lib/puppet/feature/faraday/adapter/em_synchrony/parallel_manager.rb": "ed23bb89721bfcc7be531d8a7fa1d75a",
+ "lib/puppet/feature/faraday/adapter/em_synchrony.rb": "4d9eb3f1ca759f7008c4cd51ad055ddc",
+ "lib/puppet/feature/faraday/adapter/excon.rb": "4d1bcdeeeb3623c5f5847350420102cb",
+ "lib/puppet/feature/faraday/adapter/httpclient.rb": "cffdad2f5f9f109fb62ace392fabbcf1",
+ "lib/puppet/feature/faraday/adapter/net_http.rb": "98ca2bafc840b0049e12448c12f7d9f6",
+ "lib/puppet/feature/faraday/adapter/net_http_persistent.rb": "1d8cbd07de4b3464ed42774728d866d8",
+ "lib/puppet/feature/faraday/adapter/patron.rb": "771a78d359202538a062b6e1de186b62",
+ "lib/puppet/feature/faraday/adapter/rack.rb": "70078f81411a4294bd20302f9a857120",
+ "lib/puppet/feature/faraday/adapter/test.rb": "e3177c396bb40d922e8aee81c745b173",
+ "lib/puppet/feature/faraday/adapter/typhoeus.rb": "2bb446cc26a8fce211a92670838e977c",
+ "lib/puppet/feature/faraday/adapter.rb": "c8e0aacec14e78a9d1b73bc674c147d7",
+ "lib/puppet/feature/faraday/autoload.rb": "c3825f673dcd897eccbbf0204b290342",
+ "lib/puppet/feature/faraday/connection.rb": "0ffff1f6f1996dbb7643c87e5acc482d",
+ "lib/puppet/feature/faraday/error.rb": "a5900e607b1573bb6b6549e6573e2e90",
+ "lib/puppet/feature/faraday/middleware.rb": "da7f0af70a005cabe297f64f613e04f5",
+ "lib/puppet/feature/faraday/options.rb": "d92eea6cceda8cd61a984893ba5cc25c",
+ "lib/puppet/feature/faraday/parameters.rb": "162fbb7a45756695909c49e099d6b412",
+ "lib/puppet/feature/faraday/rack_builder.rb": "e1a93cd64e1d555530b6aa31b5b7812c",
+ "lib/puppet/feature/faraday/request/authorization.rb": "a2aa3b3b22fa39e4f1fc6c87008a5e50",
+ "lib/puppet/feature/faraday/request/basic_authentication.rb": "3da9b3b931d19e9669c3b32841ececfd",
+ "lib/puppet/feature/faraday/request/instrumentation.rb": "c48435dbd1ba855f71c4f538baa7b665",
+ "lib/puppet/feature/faraday/request/multipart.rb": "dcc172d9443f53d28d06cba93de2cd7b",
+ "lib/puppet/feature/faraday/request/retry.rb": "57892cf880e6db7486daf8e015e9418a",
+ "lib/puppet/feature/faraday/request/token_authentication.rb": "aedd602972c21fccb6b672d8caaa77ac",
+ "lib/puppet/feature/faraday/request/url_encoded.rb": "7d8715e5cea35a1d3416a174e1fb492e",
+ "lib/puppet/feature/faraday/request.rb": "0d5064fe1ae944b7aa21a111ccfbaf4d",
+ "lib/puppet/feature/faraday/response/logger.rb": "0d4a6a3809bb3612715faf2b862ed147",
+ "lib/puppet/feature/faraday/response/raise_error.rb": "8544053fd9007b42885793222f917b26",
+ "lib/puppet/feature/faraday/response.rb": "6423348722701307e11f261b4ca09af8",
+ "lib/puppet/feature/faraday/upload_io.rb": "7b0b80bc2cbbfca411a867d621f7b497",
+ "lib/puppet/feature/faraday/utils.rb": "4df493a45499dbca0770f613da3d7bf6",
+ "lib/puppet/feature/faraday.rb": "61f81f20888a0a1c992c0b108c96019d",
+ "lib/puppet/feature/multipart_post.rb": "3433edf755f20e56edf3c167492a8a0f",
+ "lib/puppet/feature/multipartable.rb": "5465ba7b40057b35eccf93bd1e50180d",
+ "lib/puppet/feature/net/http/post/multipart.rb": "469d05036cd0b981b201d0fe50360f5b",
+ "lib/puppet/feature/parts.rb": "c6a86930e784e4ab7d8d022b6f64e636"
+}
\ No newline at end of file
--- /dev/null
+# Add the parent dir to the load path. This is for when
+# Aviator is not installed as a gem
+lib_path = File.dirname(__FILE__)
+$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include? lib_path
+
+require 'aviator/core'
--- /dev/null
+module Aviator
+
+ module Compatibility
+ RUBY_1_8_MODE = (not (RUBY_VERSION =~ /1\.8\.\d*/).nil?)
+ end
+
+end
+
+if Aviator::Compatibility::RUBY_1_8_MODE
+
+ class Module
+
+ alias_method :old_const_defined?, :const_defined?
+
+ def const_defined?(sym, ignore=nil)
+ old_const_defined?(sym)
+ end
+
+
+ alias_method :old_const_get, :const_get
+
+ def const_get(sym, ignore=nil)
+ old_const_get(sym)
+ end
+
+ alias_method :old_instance_methods, :instance_methods
+
+ def instance_methods(include_super=true)
+ old_instance_methods(include_super).map(&:to_sym)
+ end
+
+ end
+
+end
--- /dev/null
+require 'yaml'
+require 'json'
+require 'faraday'
+
+require "aviator/string"
+require "aviator/version"
+require "aviator/compatibility"
+require "aviator/hashish"
+require "aviator/core/request"
+require "aviator/core/request_builder"
+require "aviator/core/response"
+require "aviator/core/service"
+require "aviator/core/session"
+require "aviator/core/logger"
--- /dev/null
+require "terminal-table"
+require "aviator/core/cli/describer"
--- /dev/null
+module Aviator
+
+ class Describer
+
+ def self.describe_aviator
+ str = "Available providers:\n"
+
+ provider_names.each do |provider_name|
+ str << " #{ provider_name }\n"
+ end
+
+ str
+ end
+
+
+ def self.describe_provider(provider_name)
+ str = "Available services for #{ provider_name }:\n"
+
+ service_names(provider_name).each do |service_name|
+ str << " #{ service_name }\n"
+ end
+
+ str
+ end
+
+
+ def self.describe_request(provider_name, service_name, api_version, endpoint_type, request_name)
+ service = Aviator::Service.new :provider => provider_name, :service => service_name
+ request_class = "Aviator::#{ provider_name.camelize }::#{ service_name.camelize }::"\
+ "#{ api_version.camelize }::#{ endpoint_type.camelize }::#{ request_name.camelize }".constantize
+
+ display = ":Request => #{ request_name }\n"
+
+
+ # Build the parameters
+ params = request_class.optional_params.map{|p| [p, false]} +
+ request_class.required_params.map{|p| [p, true]}
+
+ aliases = request_class.param_aliases
+
+ if params.length > 0
+ display << "\n"
+
+ headings = ['NAME', 'REQUIRED?']
+
+ headings << 'ALIAS' if aliases.length > 0
+
+ rows = []
+ params.sort{|a,b| a[0].to_s <=> b[0].to_s }.each do |param|
+ row = [ param[0], param[1] ? 'Y' : 'N' ]
+
+ if aliases.length > 0
+ row << (aliases.find{|a,p| p == param[0] } || [''])[0]
+ end
+
+ rows << row
+ end
+
+ widths = [
+ rows.map{|row| row[0].to_s.length }.max,
+ rows.map{|row| row[1].to_s.length }.max
+ ]
+
+ widths << rows.map{|row| row[2].to_s.length }.max if aliases.length > 0
+
+ table = Terminal::Table.new(:headings => headings, :rows => rows)
+
+ table.align_column(1, :center)
+
+ display << "Parameters:\n"
+ display << " " + table.to_s.split("\n").join("\n ")
+ display << "\n"
+ end
+
+
+ # Build the sample code
+ display << "\nSample Code:\n"
+
+ display << " session.#{ service_name }_service.request(:#{ request_name })"
+
+ if params && params.length > 0
+ display << " do |params|\n"
+ params.each do |pair|
+ display << " params.#{ (aliases.find{|a,p| p == pair[0] } || pair)[0] } = value\n"
+ end
+ display << " end"
+ end
+
+ display << "\n"
+
+
+ # Build the links
+ if request_class.links && request_class.links.length > 0
+ display << "\nLinks:\n"
+
+ request_class.links.each do |link|
+ display << " #{ link[:rel] }:\n"
+ display << " #{ link[:href] }\n"
+ end
+ end
+
+ display
+ end
+
+
+ def self.describe_service(provider_name, service_name)
+ str = "Available requests for #{ provider_name } #{ service_name }_service:\n"
+
+ request_classes(provider_name, service_name).each do |klass|
+ str << " #{ klass.api_version } #{ klass.endpoint_type } #{ klass.name.split('::').last.underscore }\n"
+ end
+
+ str
+ end
+
+
+ class <<self
+ private
+
+ def provider_names
+ Pathname.new(__FILE__) \
+ .join('..', '..', '..') \
+ .children \
+ .select{|c| c.directory? && c.basename.to_s != 'core' } \
+ .map{|c| c.basename.to_s }
+ end
+
+
+ def request_classes(provider_name, service_name)
+ service = Aviator::Service.new(:provider => provider_name, :service => service_name)
+ service.request_classes
+ end
+
+
+ def service_names(provider_name)
+ Pathname.new(__FILE__) \
+ .join('..', '..', '..', provider_name) \
+ .children \
+ .select{|c| c.directory? } \
+ .map{|c| c.basename.to_s }
+ end
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class Logger < Faraday::Response::Middleware
+ extend Forwardable
+
+ def initialize(app, logger=nil)
+ super(app)
+ @logger = logger || begin
+ require 'logger'
+ ::Logger.new(self.class::LOG_FILE_PATH)
+ end
+ end
+
+
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
+
+
+ def call(env)
+ info(env[:method].to_s.upcase) { env[:url].to_s }
+ debug('REQ_HEAD') { dump_headers env[:request_headers] }
+ debug('REQ_BODY') { dump_body env[:body] }
+ super
+ end
+
+
+ def on_complete(env)
+ info('STATUS') { env[:status].to_s }
+ debug('RES_HEAD') { dump_headers env[:response_headers] }
+ debug('RES_BODY') { dump_body env[:body] }
+ end
+
+
+ def self.configure(log_file_path)
+ # Return a subclass with its logfile path set. This
+ # must be done so that different sessions can log to
+ # different paths.
+ Class.new(self) { const_set('LOG_FILE_PATH', log_file_path) }
+ end
+
+
+ private
+
+ def dump_body(body)
+ return if body.nil?
+
+ # :TODO => Make this configurable
+ body.gsub(/["']password["']:["']\w*["']/, '"password":[FILTERED_VALUE]')
+ end
+
+ def dump_headers(headers)
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("; ")
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class Request
+
+ class ApiVersionNotDefinedError < StandardError
+ def initialize
+ super "api_version is not defined."
+ end
+ end
+
+ class EndpointTypeNotDefinedError < StandardError
+ def initialize
+ super "endpoint_type is not defined."
+ end
+ end
+
+ class PathNotDefinedError < StandardError
+ def initialize
+ super "path is not defined."
+ end
+ end
+
+
+ def initialize(session_data=nil)
+ @session_data = session_data
+
+ params = self.class.params_class.new if self.class.params_class
+
+ if params
+ yield(params) if block_given?
+ validate_params(params)
+ end
+
+ @params = params
+ end
+
+
+ def anonymous?
+ self.class.anonymous?
+ end
+
+
+ def body?
+ self.class.body?
+ end
+
+
+ def headers?
+ self.class.headers?
+ end
+
+
+ def links
+ self.class.links
+ end
+
+
+ def optional_params
+ self.class.optional_params
+ end
+
+
+ def params
+ @params.dup
+ end
+
+
+ def required_params
+ self.class.required_params
+ end
+
+
+ def session_data
+ @session_data
+ end
+
+
+ def session_data?
+ !session_data.nil?
+ end
+
+
+ def querystring?
+ self.class.querystring?
+ end
+
+
+ def url?
+ self.class.url?
+ end
+
+
+ private
+
+
+ def validate_params(params)
+ required_params = self.class.required_params
+
+ required_params.each do |name|
+ raise ArgumentError.new("Missing parameter #{ name }.") if params.send(name).nil?
+ end
+ end
+
+
+ # NOTE that, because we are defining the following as class methods, when they
+ # are called, all 'instance' variables are actually defined in the descendant class,
+ # not in the instance/object. This is by design since we want to keep these attributes
+ # within the class and because they don't change between instances anyway.
+ class << self
+
+ def anonymous?
+ respond_to?(:anonymous) && anonymous == true
+ end
+
+
+ def body?
+ instance_methods.include? :body
+ end
+
+
+ def headers?
+ instance_methods.include? :headers
+ end
+
+
+ def links
+ @links ||= []
+ end
+
+
+ def param_aliases
+ @param_aliases ||= {}
+ end
+
+
+ def params_class
+ all_params = required_params + optional_params
+
+ if all_params.length > 0 && @params_class.nil?
+ @params_class = build_params_class(all_params, self.param_aliases)
+ end
+
+ @params_class
+ end
+
+
+ def optional_params
+ @optional_params ||= []
+ end
+
+
+ def querystring?
+ instance_methods.include? :querystring
+ end
+
+
+ def required_params
+ @required_params ||= []
+ end
+
+
+ def url?
+ instance_methods.include? :url
+ end
+
+
+ private
+
+
+ def build_params_class(all_params, param_aliases)
+ Struct.new(*all_params) do
+ alias :param_getter :[]
+ alias :param_setter :[]=
+
+ define_method :[] do |key|
+ key = param_aliases[key.to_sym] if param_aliases.keys.include? key.to_sym
+ param_getter(key)
+ end
+
+ define_method :[]= do |key, value|
+ key = param_aliases[key.to_sym] if param_aliases.keys.include? key.to_sym
+ param_setter(key, value)
+ end
+
+ param_aliases.each do |param_alias, param_name|
+ define_method param_alias do
+ param_getter(param_name)
+ end
+
+ define_method "#{ param_alias }=" do |value|
+ param_setter(param_name, value)
+ end
+ end
+ end
+ end
+
+
+ def link(rel, href)
+ links << { :rel => rel, :href => href }
+ end
+
+
+ def meta(attr_name, attr_value)
+ eigenclass = class << self; self; end
+ eigenclass.send(:define_method, attr_name) do
+ attr_value
+ end
+
+ define_method(attr_name) do
+ self.class.send(attr_name)
+ end
+ end
+
+
+ def param(param_name, opts={})
+ opts = Hashish.new(opts)
+ list = (opts[:required] == false ? optional_params : required_params)
+ list << param_name unless optional_params.include?(param_name)
+
+ if opts[:alias]
+ self.param_aliases[opts[:alias]] = param_name
+ end
+ end
+
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class BaseRequestNotFoundError < StandardError
+ attr_reader :base_request_hierarchy
+
+ def initialize(base_hierarchy)
+ @base_request_hierarchy = base_hierarchy
+ super("#{ base_request_hierarchy } could not be found!")
+ end
+ end
+
+
+ class RequestAlreadyDefinedError < StandardError
+ attr_reader :namespace,
+ :request_name
+
+ def initialize(namespace, request_name)
+ @namespace = namespace
+ @request_name = request_name
+ super("#{ namespace }::#{ request_name } is already defined")
+ end
+ end
+
+
+ class RequestBuilder
+
+ class << self
+
+ def define_request(root_namespace, request_name, options, &block)
+ base_klass = get_request_class(root_namespace, options[:inherit])
+
+ klass = Class.new(base_klass, &block)
+
+ namespace_arr = [
+ klass.provider,
+ klass.service,
+ klass.api_version,
+ klass.endpoint_type
+ ]
+
+ namespace = namespace_arr.inject(root_namespace) do |namespace, sym|
+ const_name = sym.to_s.camelize
+ namespace.const_set(const_name, Module.new) unless namespace.const_defined?(const_name, false)
+ namespace.const_get(const_name, false)
+ end
+
+ klassname = request_name.to_s.camelize
+
+ if namespace.const_defined?(klassname, false)
+ raise RequestAlreadyDefinedError.new(namespace, klassname)
+ end
+
+ namespace.const_set(klassname, klass)
+ end
+
+
+ def get_request_class(root_namespace, request_class_arr)
+ request_class_arr.inject(root_namespace) do |namespace, sym|
+ namespace.const_get(sym.to_s.camelize, false)
+ end
+ rescue NameError => e
+ arr = ['..', '..'] + request_class_arr
+ arr[-1,1] = arr.last.to_s + '.rb'
+ path = Pathname.new(__FILE__).join(*arr.map{|i| i.to_s }).expand_path
+
+ if path.exist?
+ require path
+ request_class_arr.inject(root_namespace) do |namespace, sym|
+ namespace.const_get(sym.to_s.camelize, false)
+ end
+ else
+ raise BaseRequestNotFoundError.new(request_class_arr)
+ end
+ end
+
+ end
+
+ end
+
+
+ class << self
+
+ def define_request(request_name, options={ :inherit => [:request] }, &block)
+ RequestBuilder.define_request self, request_name, options, &block
+ end
+
+ end # class << self
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ class Response
+ extend Forwardable
+
+ def_delegators :@response, :headers, :status
+
+ attr_reader :request
+
+ def initialize(response, request)
+ @response = response
+ @request = request
+ end
+
+
+ def body
+ if raw_body.length > 0
+ if Aviator::Compatibility::RUBY_1_8_MODE
+ clean_body = raw_body.gsub(/\\ /, ' ')
+ else
+ clean_body = raw_body
+ end
+
+ Hashish.new(JSON.parse(clean_body))
+ else
+ Hashish.new({})
+ end
+ end
+
+
+ private
+
+ def raw_body
+ @response.body
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class Service
+
+ class AccessDetailsNotDefinedError < StandardError
+ def initialize
+ super ":access_details is not defined."
+ end
+ end
+
+ class ProviderNotDefinedError < StandardError
+ def initialize
+ super ":provider is not defined."
+ end
+ end
+
+ class ServiceNameNotDefinedError < StandardError
+ def initialize
+ super ":service is not defined."
+ end
+ end
+
+ class SessionDataNotProvidedError < StandardError
+ def initialize
+ super "default_session_data is not initialized and no session data was "\
+ "provided in the method call."
+ end
+ end
+
+
+ class UnknownRequestError < StandardError
+ def initialize(request_name)
+ super "Unknown request #{ request_name }."
+ end
+ end
+
+
+ class MissingServiceEndpointError < StandardError
+ def initialize(service_name, request_name)
+ request_name = request_name.to_s.split('::').last.underscore
+ super "The session's service catalog does not have an entry for the #{ service_name } "\
+ "service. Therefore, I don't know to which base URL the request should be sent. "\
+ "This may be because you are using a default or unscoped token. If this is not your "\
+ "intention, please authenticate with a scoped token. If using a default token is your "\
+ "intention, make sure to provide a base url when you call the request. For :example => \n\n"\
+ "session.#{ service_name }_service.request :#{ request_name }, :base_url => 'http://myenv.com:9999/v2.0' do |params|\n"\
+ " params[:example1] = 'example1'\n"\
+ " params[:example2] = 'example2'\n"\
+ "end\n\n"
+ end
+ end
+
+ attr_accessor :default_session_data
+
+ attr_reader :service,
+ :provider
+
+
+ def initialize(opts={})
+ @provider = opts[:provider] || (raise ProviderNotDefinedError.new)
+ @service = opts[:service] || (raise ServiceNameNotDefinedError.new)
+ @log_file = opts[:log_file]
+
+ @default_session_data = opts[:default_session_data]
+
+ load_requests
+ end
+
+
+ def request(request_name, options={}, ¶ms)
+ session_data = options[:session_data] || default_session_data
+
+ raise SessionDataNotProvidedError.new unless session_data
+
+ [:base_url].each do |k|
+ session_data[k] = options[k] if options[k]
+ end
+
+ request_class = find_request(request_name, session_data, options[:endpoint_type])
+
+ raise UnknownRequestError.new(request_name) unless request_class
+
+ request = request_class.new(session_data, ¶ms)
+
+ response = http_connection.send(request.http_method) do |r|
+ r.url request.url
+ r.headers.merge!(request.headers) if request.headers?
+ r.query = request.querystring if request.querystring?
+ r.body = JSON.generate(request.body) if request.body?
+ end
+
+ Aviator::Response.send(:new, response, request)
+ end
+
+
+ def request_classes
+ @request_classes
+ end
+
+
+ private
+
+
+ def http_connection
+ @http_connection ||= Faraday.new do |conn|
+ conn.use Logger.configure(log_file) if log_file
+ conn.adapter Faraday.default_adapter
+
+ conn.headers['Content-Type'] = 'application/json'
+ end
+ end
+
+
+ # Candidate for extraction to aviator/openstack
+ def find_request(name, session_data, endpoint_type=nil)
+ endpoint_types = if endpoint_type
+ [endpoint_type.to_s.camelize]
+ else
+ ['Public', 'Admin']
+ end
+
+ namespace = Aviator.const_get(provider.camelize) \
+ .const_get(service.camelize)
+
+ version = infer_version(session_data, name).to_s.camelize
+
+ return nil unless version && namespace.const_defined?(version)
+
+ namespace = namespace.const_get(version, name)
+
+ endpoint_types.each do |endpoint_type|
+ name = name.to_s.camelize
+
+ next unless namespace.const_defined?(endpoint_type)
+ next unless namespace.const_get(endpoint_type).const_defined?(name)
+
+ return namespace.const_get(endpoint_type).const_get(name)
+ end
+
+ nil
+ end
+
+
+ # Candidate for extraction to aviator/openstack
+ def infer_version(session_data, request_name='sample_request')
+ if session_data.has_key?(:auth_service) && session_data[:auth_service][:api_version]
+ session_data[:auth_service][:api_version].to_sym
+
+ elsif session_data.has_key?(:auth_service) && session_data[:auth_service][:host_uri]
+ m = session_data[:auth_service][:host_uri].match(/(v\d+)\.?\d*/)
+ return m[1].to_sym unless m.nil?
+
+ elsif session_data.has_key? :base_url
+ m = session_data[:base_url].match(/(v\d+)\.?\d*/)
+ return m[1].to_sym unless m.nil?
+
+ elsif session_data.has_key? :access
+ service_spec = session_data[:access][:serviceCatalog].find{|s| s[:type] == service }
+ raise MissingServiceEndpointError.new(service.to_s, request_name) unless service_spec
+ version = service_spec[:endpoints][0][:publicURL].match(/(v\d+)\.?\d*/)
+ version ? version[1].to_sym : :v1
+ end
+ end
+
+
+ def load_requests
+ # :TODO => This should be determined by a provider-specific module.
+ # e.g. Aviator::OpenStack::requests_base_dir
+ request_file_paths = Dir.glob(Pathname.new(__FILE__).join(
+ '..',
+ '..',
+ provider.to_s,
+ service.to_s,
+ '**',
+ '*.rb'
+ ).expand_path
+ )
+
+ request_file_paths.each{ |path| require path }
+
+ constant_parts = request_file_paths \
+ .map{|rf| rf.to_s.match(/#{provider}\/#{service}\/([\w\/]+)\.rb$/) } \
+ .map{|rf| rf[1].split('/').map{|c| c.camelize }.join('::') }
+
+ @request_classes = constant_parts.map do |cp|
+ "Aviator::#{provider.camelize}::#{service.camelize}::#{cp}".constantize
+ end
+ end
+
+
+ def log_file
+ @log_file
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class Session
+
+ class AuthenticationError < StandardError
+ def initialize(last_auth_body)
+ super("Authentication failed. The server returned #{ last_auth_body }")
+ end
+ end
+
+
+ class EnvironmentNotDefinedError < ArgumentError
+ def initialize(path, env)
+ super("The environment '#{ env }' is not defined in #{ path }.")
+ end
+ end
+
+ class InitializationError < StandardError
+ def initialize
+ super("The session could not find :session_dump, :config_file, and " \
+ ":config in the constructor arguments provided")
+ end
+ end
+
+ class InvalidConfigFilePathError < ArgumentError
+ def initialize(path)
+ super("The config file at #{ path } does not exist!")
+ end
+ end
+
+
+ class NotAuthenticatedError < StandardError
+ def initialize
+ super("Session is not authenticated. Please authenticate before proceeding.")
+ end
+ end
+
+
+ class ValidatorNotDefinedError < StandardError
+ def initialize
+ super("The validator request name is not defined for this session object.")
+ end
+ end
+
+
+ def initialize(opts={})
+ if opts.has_key? :session_dump
+ initialize_with_dump(opts[:session_dump])
+ elsif opts.has_key? :config_file
+ initialize_with_config(opts[:config_file], opts[:environment])
+ elsif opts.has_key? :config
+ initialize_with_hash(opts[:config])
+ else
+ raise InitializationError.new
+ end
+
+ @log_file = opts[:log_file]
+ end
+
+
+ def authenticate(&block)
+ block ||= lambda do |params|
+ environment[:auth_credentials].each do |key, value|
+ params[key] = value
+ end
+ end
+
+ response = auth_service.request environment[:auth_service][:request].to_sym, &block
+
+ if response.status == 200
+ @auth_info = response.body
+ update_services_session_data
+ else
+ raise AuthenticationError.new(response.body)
+ end
+ end
+
+
+ def authenticated?
+ !auth_info.nil?
+ end
+
+
+ def dump
+ JSON.generate({
+ :environment => environment,
+ :auth_info => auth_info
+ })
+ end
+
+
+ def load(session_dump)
+ initialize_with_dump(session_dump)
+ update_services_session_data
+ self
+ end
+
+
+ def method_missing(name, *args, &block)
+ service_name_parts = name.to_s.match(/^(\w+)_service$/)
+
+ if service_name_parts
+ get_service_obj(service_name_parts[1])
+ else
+ super name, *args, &block
+ end
+ end
+
+
+ def self.load(session_dump, opts={})
+ opts[:session_dump] = session_dump
+
+ new(opts)
+ end
+
+
+ def validate
+ raise NotAuthenticatedError.new unless authenticated?
+ raise ValidatorNotDefinedError.new unless environment[:auth_service][:validator]
+
+ auth_with_bootstrap = auth_info.merge({ :auth_service => environment[:auth_service] })
+
+ response = auth_service.request environment[:auth_service][:validator].to_sym, :session_data => auth_with_bootstrap
+
+ response.status == 200 || response.status == 203
+ end
+
+
+ private
+
+
+ def auth_info
+ @auth_info
+ end
+
+
+ def auth_service
+ @auth_service ||= Service.new(
+ :provider => environment[:provider],
+ :service => environment[:auth_service][:name],
+ :default_session_data => { :auth_service => environment[:auth_service] },
+ :log_file => log_file
+ )
+ end
+
+
+ def environment
+ @environment
+ end
+
+
+ def get_service_obj(service_name)
+ raise NotAuthenticatedError.new unless self.authenticated?
+
+ @services ||= {}
+
+ @services[service_name] ||= Service.new(
+ :provider => environment[:provider],
+ :service => service_name,
+ :default_session_data => auth_info,
+ :log_file => log_file
+ )
+
+ @services[service_name]
+ end
+
+
+ def initialize_with_config(config_path, environment)
+ raise InvalidConfigFilePathError.new(config_path) unless Pathname.new(config_path).file?
+
+ config = Hashish.new(YAML.load_file(config_path))
+
+ raise EnvironmentNotDefinedError.new(config_path, environment) unless config[environment]
+
+ @environment = config[environment]
+ end
+
+
+ def initialize_with_dump(session_dump)
+ session_info = Hashish.new(JSON.parse(session_dump))
+ @environment = session_info[:environment]
+ @auth_info = session_info[:auth_info]
+ end
+
+ def initialize_with_hash(hash_obj)
+ @environment = Hashish.new(hash_obj)
+ end
+
+ def log_file
+ @log_file
+ end
+
+
+ def update_services_session_data
+ return unless @services
+
+ @services.each do |name, obj|
+ obj.default_session_data = auth_info
+ end
+ end
+
+ end
+
+end
--- /dev/null
+# Hash-ish!
+#
+# This class is implemented using composition rather than inheritance so
+# that we have control over what operations it exposes to peers.
+class Hashish
+ include Enumerable
+
+ def initialize(hash={})
+ @hash = hash
+ hashishify_values
+ end
+
+ def ==(other_obj)
+ other_obj.class == self.class &&
+ other_obj.hash == self.hash
+ end
+
+ def [](key)
+ @hash[normalize(key)]
+ end
+
+ def []=(key, value)
+ @hash[normalize(key)] = value
+ end
+
+ def each(&block)
+ @hash.each(&block)
+ end
+
+ def empty?
+ @hash.empty?
+ end
+
+ def has_key?(name)
+ @hash.has_key? normalize(name)
+ end
+
+ def hash
+ @hash
+ end
+
+ def keys
+ @hash.keys
+ end
+
+ def length
+ @hash.length
+ end
+
+ def merge(other_hash)
+ Hashish.new(@hash.merge(other_hash))
+ end
+
+ def merge!(other_hash)
+ @hash.merge! other_hash
+ self
+ end
+
+ def to_json(obj)
+ @hash.to_json(obj)
+ end
+
+ def to_s
+ str = "{"
+ @hash.each do |key, value|
+ if value.kind_of? String
+ value = "'#{value}'"
+ elsif value.nil?
+ value = "nil"
+ elsif value.kind_of? Array
+ value = "[#{value.join(", ")}]"
+ end
+
+ str += " #{key}: #{value},"
+ end
+
+ str = str[0...-1] + " }"
+ str
+ end
+
+ private
+
+ # Hashishify all the things!
+ def hashishify_values
+ @hash.each do |key, value|
+ if @hash[key].kind_of? Hash
+ @hash[key] = Hashish.new(value)
+ elsif @hash[key].kind_of? Array
+ @hash[key].each_index do |index|
+ element = @hash[key][index]
+ if element.kind_of? Hash
+ @hash[key][index] = Hashish.new(element)
+ end
+ end
+ end
+ end
+ end
+
+ def normalize(key)
+ if @hash.has_key? key
+ key
+ elsif key.is_a? String
+ key.to_sym
+ elsif key.is_a? Symbol
+ key.to_s
+ else
+ key
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :base, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :endpoint_type, :admin
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :base do
+
+ meta :provider, :openstack
+ meta :service, :common
+ meta :api_version, :v2
+ meta :endpoint_type, :public
+
+ def headers
+ {}.tap do |h|
+ h['X-Auth-Token'] = session_data[:access][:token][:id] unless self.anonymous?
+ end
+ end
+
+
+ private
+
+
+ def base_url
+ if session_data[:base_url]
+ session_data[:base_url]
+ elsif service_spec = session_data[:access][:serviceCatalog].find { |s| s[:type] == service.to_s }
+ service_spec[:endpoints][0]["#{ endpoint_type }URL".to_sym]
+ elsif session_data[:auth_service] && session_data[:auth_service][:host_uri] && session_data[:auth_service][:api_version]
+ "#{ session_data[:auth_service][:host_uri] }/v2.0"
+ elsif session_data[:auth_service] && session_data[:auth_service][:host_uri]
+ session_data[:auth_service][:host_uri]
+ else
+ raise Aviator::Service::MissingServiceEndpointError.new(service.to_s, self.class)
+ end
+ end
+
+
+ def params_to_querystring(param_names)
+ filters = []
+
+ param_names.each do |param_name|
+ filters << "#{ param_name }=#{ params[param_name] }" if params[param_name]
+ end
+
+ filters.empty? ? "" : "?#{ filters.join('&') }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :confirm_server_resize, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Confirm_Resized_Server-d1e3868.html'
+
+ param :id, :required => true
+
+
+ def body
+ {
+ :confirmResize => nil
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_network, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-compute.html#ext-os-networks'
+
+
+ param :label, :required => true
+ param :bridge, :required => false
+ param :bridge_interface, :required => false
+ param :cidr, :required => false
+ param :cidr_v6, :required => false
+ param :dns1, :required => false
+ param :dns2, :required => false
+ param :gateway, :required => false
+ param :gateway_v6, :required => false
+ param :multi_host, :required => false
+ param :project_id, :required => false
+ param :vlan, :required => false
+
+
+ def body
+ p = {
+ :network => {
+ :label => params[:label]
+ }
+ }
+
+ optional_params.each do |key|
+ p[:network][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/os-networks"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_host_details, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref.html#ext-os-hosts'
+
+ param :host_name, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/os-hosts/#{ params[:host_name] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_hosts, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref.html#ext-os-hosts'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/nova/+bug/1224763'
+
+ param :service, :required => false
+ param :zone, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ url = "#{ base_url }/os-hosts"
+
+ filters = []
+
+ optional_params.each do |param_name|
+ filters << "#{ param_name }=#{ params[param_name] }" if params[param_name]
+ end
+
+ url += "?#{ filters.join('&') }" unless filters.empty?
+
+ url
+ end
+
+ end
+
+end
+
--- /dev/null
+module Aviator
+
+ define_request :lock_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_lock_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :lock => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :migrate_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_migrate_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :migrate => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :reset_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_os-resetState_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+ param :state, :required => true
+
+
+ def body
+ {
+ 'os-resetState' => {
+ 'state' => params[:state]
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :resize_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Resize_Server-d1e3707.html'
+
+ param :id, :required => true
+ param :name, :required => true
+ param :flavorRef, :required => true, :alias => :flavor_ref
+
+
+ def body
+ {
+ :resize => {
+ :name => params[:name],
+ :flavorRef => params[:flavorRef]
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :revert_server_resize do
+
+ meta :provider, :openstack
+ meta :service, :compute
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Revert_Resized_Server-d1e4024.html'
+
+ param :id, :required => true
+
+
+ def body
+ {
+ :revertResize => nil
+ }
+ end
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ service_spec = session_data[:access][:serviceCatalog].find{|s| s[:type] == service.to_s }
+ "#{ service_spec[:endpoints][0][:adminURL] }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :unlock_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_unlock_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :unlock => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :change_admin_password, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Change_Password-d1e3234.html'
+
+ link 'additional spec',
+ 'https://answers.launchpad.net/nova/+question/228462'
+
+ param :adminPass, :required => true, :alias => :admin_pass
+ param :id, :required => true
+
+
+ def body
+ p = {
+ :changePassword => {
+ :adminPass => params[:adminPass]
+ }
+ }
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_image, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Create_Image-d1e4655.html'
+
+ param :id, :required => true
+ param :metadata, :required => false
+ param :name, :required => true
+
+
+ def body
+ p = {
+ :createImage => {
+ :name => params[:name]
+ }
+ }
+
+ [:metadata].each do |key|
+ p[:createImage][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/CreateServers.html'
+
+ param :accessIPv4, :required => false, :alias => :access_ipv4
+ param :accessIPv6, :required => false, :alias => :access_ipv6
+ param :adminPass, :required => false, :alias => :admin_pass
+ param :imageRef, :required => true, :alias => :image_ref
+ param :flavorRef, :required => true, :alias => :flavor_ref
+ param :metadata, :required => false
+ param :name, :required => true
+ param :networks, :required => false
+ param :personality, :required => false
+
+
+ def body
+ p = {
+ :server => {
+ :flavorRef => params[:flavorRef],
+ :imageRef => params[:imageRef],
+ :name => params[:name]
+ }
+ }
+
+ [:adminPass, :metadata, :personality, :networks, :accessIPv4, :accessIPv6].each do |key|
+ p[:server][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_image, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Image-d1e4957.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id]}"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :delete_image_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Metadata_Item-d1e5790.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Server-d1e2883.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_server_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Metadata_Item-d1e5790.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_flavor_details, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Flavor_Details-d1e4317.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/flavors/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_image_details, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Image_Details-d1e4848.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id]}"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :get_image_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Metadata_Item-d1e5507.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_network_details, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-compute.html#ext-os-networks'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/os-networks/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Server_Details-d1e2623.html'
+
+ param :id, :required => true
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_server_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Metadata_Item-d1e5507.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_addresses, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Addresses-d1e3014.html'
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Addresses_by_Network-d1e3118.html'
+
+
+ param :id, :required => true
+ param :networkID, :required => false, :alias => :network_id
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ url = "#{ base_url }/servers/#{ params[:id] }/ips"
+ url += "/#{ params[:networkID] }" if params[:networkID]
+ url
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_flavors, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Flavors-d1e4188.html'
+
+ param :details, :required => false
+ param :minDisk, :required => false, :alias => :min_disk
+ param :minRam, :required => false, :alias => :min_ram
+ param :marker, :required => false
+ param :limit, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/flavors"
+ str += "/detail" if params[:details]
+ str += params_to_querystring(optional_params + required_params - [:details])
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_image_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Metadata-d1e5089.html'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_images, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Images-d1e4435.html'
+
+ param :details, :required => false
+ param :server, :required => false
+ param :name, :required => false
+ param :status, :required => false
+ param 'changes-since', :required => false, :alias => :changes_since
+ param :marker, :required => false
+ param :limit, :required => false
+ param :type, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/images"
+ str += "/detail" if params[:details]
+ str += params_to_querystring(optional_params + required_params - [:details])
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :list_networks, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-compute.html#ext-os-networks'
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/os-networks"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_server_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Metadata-d1e5089.html'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_servers, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Servers-d1e2078.html'
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/GET_listServers_v2__tenant_id__servers_compute_servers.html'
+
+ link 'github :issue => getting all servers',
+ 'https://github.com/aviator/aviator/issues/35'
+
+ link 'related mailing list discussion',
+ 'https://lists.launchpad.net/openstack/msg24695.html'
+
+ param :all_tenants, :required => false
+ param :details, :required => false
+ param :flavor, :required => false
+ param :image, :required => false
+ param :limit, :required => false
+ param :marker, :required => false
+ param :server, :required => false
+ param :status, :required => false
+ param 'changes-since', :required => false, :alias => :changes_since
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/servers"
+ str += "/detail" if params[:details]
+
+ filters = []
+
+ (optional_params + required_params - [:details]).each do |param_name|
+ value = param_name == :all_tenants && params[param_name] ? 1 : params[param_name]
+ filters << "#{ param_name }=#{ value }" if value
+ end
+
+ str += "?#{ filters.join('&') }" unless filters.empty?
+
+ str
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :pause_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_pause_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :pause => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :reboot_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Reboot_Server-d1e3371.html'
+
+ param :id, :required => true
+ param :type, :required => false
+
+
+ def body
+ p = {
+ :reboot => {
+ :type => params[:type] || 'SOFT'
+ }
+ }
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :rebuild_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Rebuild_Server-d1e3538.html'
+
+ param :accessIPv4, :required => false, :alias => :access_ipv4
+ param :accessIPv6, :required => false, :alias => :access_ipv6
+ param :adminPass, :required => true, :alias => :admin_pass
+ param :id, :required => true
+ param :imageRef, :required => true, :alias => :image_ref
+ param :metadata, :required => false
+ param :name, :required => true
+ param :personality, :required => false
+
+
+ def body
+ p = {
+ :rebuild => {
+ :adminPass => params[:adminPass],
+ :imageRef => params[:imageRef],
+ :name => params[:name]
+ }
+ }
+
+ [:accessIPv4, :accessIPv6, :metadata, :personality].each do |key|
+ p[:rebuild][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :resume_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_resume_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :resume => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v2/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :set_image_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Create_or_Replace_Metadata-d1e5358.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :set_server_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Create_or_Replace_Metadata-d1e5358.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :suspend_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_suspend_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :suspend => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :unpause_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_unpause_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :unpause => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_image_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Update_Metadata-d1e5208.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/ServerUpdate.html'
+
+ param :accessIPv4, :required => false, :alias => :access_ipv4
+ param :accessIPv6, :required => false, :alias => :access_ipv6
+ param :id, :required => true
+ param :name, :required => false
+
+
+ def body
+ p = {
+ :server => { }
+ }
+
+ [:name, :accessIPv4, :accessIPv6].each do |key|
+ p[:server][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_server_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Update_Metadata-d1e5208.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :add_role_to_user_on_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/PUT_addRolesToUserOnTenant_v2.0_tenants__tenantId__users__userId__roles_OS-KSADM__roleId__.html'
+
+
+ param :tenant_id, :required => true
+ param :user_id, :required => true
+ param :role_id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ p = params
+ "#{ base_url }/tenants/#{ p[:tenant_id] }/users/#{ p[:user_id] }/roles/OS-KSADM/#{ p[:role_id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/'
+
+
+ param :name, :required => true
+ param :description, :required => true
+ param :enabled, :required => true
+
+
+ def body
+ {
+ :tenant => {
+ :name => params[:name],
+ :description => params[:description],
+ :enabled => params[:enabled]
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/tenants"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :create_user do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_addUser_v2.0_users_.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1110435'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1226466'
+
+
+ param :name, :required => true
+ param :password, :required => true
+
+ param :email, :required => false
+ param :enabled, :required => false
+ param :tenantId, :required => false, :alias => :tenant_id
+
+
+ def body
+ p = {
+ :user => {}
+ }
+
+ (required_params + optional_params).each do |key|
+ p[:user][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ service_spec = session_data[:access][:serviceCatalog].find{|s| s[:type] == 'identity' }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users"
+ end
+
+ end
+
+end
+
--- /dev/null
+module Aviator
+
+ define_request :delete_role_from_user_on_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/DELETE_deleteRoleFromUserOnTenant_v2.0_tenants__tenantId__users__userId__roles_OS-KSADM__roleId__.html'
+
+
+ param :tenant_id, :required => true
+ param :user_id, :required => true
+ param :role_id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ p = params
+ "#{ base_url }/tenants/#{ p[:tenant_id] }/users/#{ p[:user_id] }/roles/OS-KSADM/#{ p[:role_id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/DELETE_deleteTenant_v2.0_tenants__tenantId__.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/tenants/#{ params[:id]}"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_user do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/DELETE_deleteUser_v2.0_users__userId__.html'
+
+ param :id, :required => true
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ service_spec = session_data[:access][:serviceCatalog].find{|s| s[:type] == service.to_s }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users/#{ params[:id]}"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_tenant_by_id, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listUsers_v2.0_users_.html'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/tenants/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_tenants, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listTenants_v2.0_tenants_Tenant_Operations.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1218601'
+
+
+ param :marker, :required => false
+ param :limit, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/tenants"
+ str += params_to_querystring(optional_params + required_params)
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :list_users do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listUsers_v2.0_users_.html'
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ service_spec = session_data[:access][:serviceCatalog].find{|s| s[:type] == 'identity' }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_updateTenant_v2.0_tenants__tenantId__.html'
+
+
+ param :id, :required => true
+ param :name, :required => false
+ param :enabled, :required => false
+ param :description, :required => false
+
+
+ def body
+ p = {
+ :tenant => {}
+ }
+
+ [:name, :enabled, :description].each do |key|
+ p[:tenant][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/tenants/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_user do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_updateUser_v2.0_users__userId__.html'
+
+ link 'bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1226475'
+
+ param :id, :required => true
+ param :name, :required => false
+ param :password, :required => false
+ param :email, :required => false
+ param :enabled, :required => false
+ param :tenantId, :required => false, :alias => :tenant_id
+
+
+ def body
+ p = {
+ :user => {}
+ }
+
+ optional_params.each do |key|
+ p[:user][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ service_spec = session_data[:access][:serviceCatalog].find { |s| s[:type] == service.to_s }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_token, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :anonymous, true
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1208607'
+
+
+ param :username, :required => false
+ param :password, :required => false
+ param :tokenId, :required => false, :alias => :token_id
+ param :tenantName, :required => false, :alias => :tenant_name
+ param :tenantId, :required => false, :alias => :tenant_id
+
+
+ def body
+ p = if params[:tokenId]
+ {
+ :auth => {
+ :token => {
+ :id => params[:tokenId]
+ }
+ }
+ }
+ else
+ {
+ :auth => {
+ :passwordCredentials => {
+ :username => params[:username],
+ :password => params[:password]
+ }
+ }
+ }
+ end
+
+ p[:auth][:tenantName] = params[:tenantName] if params[:tenantName]
+ p[:auth][:tenantId] = params[:tenantId] if params[:tenantId]
+
+ p
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ url = session_data[:auth_service][:host_uri]
+ url += '/v2.0' if (URI(url).path =~ /^\/?\w+/).nil?
+ url += "/tokens"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :list_tenants, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listTenants_v2.0_tokens_tenants_.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1218601'
+
+
+ param :marker, :required => false
+ param :limit, :required => false
+
+
+ def url
+ str = "#{ base_url }/tenants"
+ str += params_to_querystring(optional_params + required_params)
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :identity
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v2.0/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_public_images, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :image
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.openstack.org/api/openstack-image-service/1.1/content/requesting-a-list-of-public-vm-images.html'
+
+ param :name, :required => false
+ param :container_format, :required => false
+ param :disk_format, :required => false
+ param :status, :required => false
+ param :size_min, :required => false
+ param :size_max, :required => false
+ param :sort_key, :required => false
+ param :sort_dir, :required => false
+
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ uri = URI(base_url)
+ url = "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/images"
+
+ filters = []
+
+ optional_params.each do |param_name|
+ filters << "#{ param_name }=#{ params[param_name] }" if params[param_name]
+ end
+
+ url += "?#{ filters.join('&') }" unless filters.empty?
+
+ url
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :image
+ meta :api_version, :v1
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_projects, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :metering
+ meta :api_version, :v1
+ meta :endpoint_type, :admin
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/projects"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/POST_createVolume_v1__tenant_id__volumes_v1__tenant_id__volumes.html'
+
+ param :display_name, :required => true
+ param :display_description, :required => true
+ param :size, :required => true
+ param :volume_type, :required => false
+ param :availability_zone, :required => false
+ param :snapshot_id, :required => false
+ param :metadata, :required => false
+
+ def body
+ p = {
+ :volume => {
+ :display_name => params[:display_name],
+ :display_description => params[:display_description],
+ :size => params[:size]
+ }
+ }
+
+ optional_params.each do |key|
+ p[:volume][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+ def headers
+ super
+ end
+
+ def http_method
+ :post
+ end
+
+ def url
+ "#{ base_url }/volumes"
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/DELETE_deleteVolume_v1__tenant_id__volumes__volume_id__v1__tenant_id__volumes.html'
+
+ param :id, :required => true
+
+ def headers
+ super
+ end
+
+ def http_method
+ :delete
+ end
+
+ def url
+ "#{ base_url }/volumes/#{ params[:id] }"
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+ meta :provider, :openstack
+ meta :service, :volume
+ meta :api_version, :v1
+ meta :endpoint_type, :public
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/GET_getVolume_v1__tenant_id__volumes__volume_id__v1__tenant_id__volumes.html'
+
+ param :id, :required => true
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ "#{ base_url }/volumes/#{ params[:id] }"
+ end
+
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_volume_types, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :provider, :openstack
+ meta :service, :volume
+ meta :api_version, :v1
+ meta :endpoint_type, :public
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/GET_getVolumeTypes_v1__tenant_id__types_v1__tenant_id__types.html'
+
+ param :extra_specs, :required => false
+ param :name, :required => false
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ "#{ base_url }/types"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_volumes, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/GET_getVolumesSimple_v1__tenant_id__volumes_v1__tenant_id__volumes.html'
+
+ param :all_tenants, :required => false
+ param :details, :required => false
+ param :status, :required => false
+ param :availability_zone, :required => false
+ param :bootable, :required => false
+ param :display_name, :required => false
+ param :display_description, :required => false
+ param :volume_type, :required => false
+ param :snapshot_id, :required => false
+ param :size, :required => false
+
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ str = "#{ base_url }/volumes"
+ str += "/detail" if params[:details]
+
+ filters = []
+
+ (optional_params + required_params - [:details]).each do |param_name|
+ value = param_name == :all_tenants && params[param_name] ? 1 : params[param_name]
+ filters << "#{ param_name }=#{ value }" if value
+ end
+
+ str += "?#{ filters.join('&') }" unless filters.empty?
+
+ str
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/PUT_renameVolume_v1__tenant_id__volumes__volume_id__v1__tenant_id__volumes.html'
+
+ param :id, :required => true
+ param :display_name, :required => false
+ param :display_description, :required => false
+
+
+ def body
+ p = { :volume => {} }
+
+ [:display_name, :display_description].each do |key|
+ p[:volume][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/volumes/#{ params[:id] }"
+ end
+
+ end
+
+
+end
--- /dev/null
+class String
+
+ def camelize
+ word = self.slice(0,1).capitalize + self.slice(1..-1)
+ word.gsub(/_([a-zA-Z\d])/) { "#{$1.capitalize}" }
+ end
+
+ def constantize
+ self.split("::").inject(Object) do |namespace, sym|
+ namespace.const_get(sym.to_s.camelize, false)
+ end
+ end
+
+ def underscore
+ self.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
+ end
+
+end
--- /dev/null
+module Aviator
+ VERSION = "0.0.7"
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2012 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+# Concatenate together multiple IO objects into a single, composite IO object
+# for purposes of reading as a single stream.
+#
+# Usage:
+#
+# crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three'))
+# puts crio.read # => "onetwothree"
+#
+class CompositeReadIO
+ # Create a new composite-read IO from the arguments, all of which should
+ # respond to #read in a manner consistent with IO.
+ def initialize(*ios)
+ @ios = ios.flatten
+ @index = 0
+ end
+
+ # Read from IOs in order until `length` bytes have been received.
+ def read(length = nil, outbuf = nil)
+ got_result = false
+ outbuf = outbuf ? outbuf.replace("") : ""
+
+ while io = current_io
+ if result = io.read(length)
+ got_result ||= !result.nil?
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
+ outbuf << result
+ length -= result.length if length
+ break if length == 0
+ end
+ advance_io
+ end
+ (!got_result && length) ? nil : outbuf
+ end
+
+ def rewind
+ @ios.each { |io| io.rewind }
+ @index = 0
+ end
+
+ private
+
+ def current_io
+ @ios[@index]
+ end
+
+ def advance_io
+ @index += 1
+ end
+end
+
+# Convenience methods for dealing with files and IO that are to be uploaded.
+class UploadIO
+ # Create an upload IO suitable for including in the params hash of a
+ # Net::HTTP::Post::Multipart.
+ #
+ # Can take two forms. The first accepts a filename and content type, and
+ # opens the file for reading (to be closed by finalizer).
+ #
+ # The second accepts an already-open IO, but also requires a third argument,
+ # the filename from which it was opened (particularly useful/recommended if
+ # uploading directly from a form in a framework, which often save the file to
+ # an arbitrarily named RackMultipart file in /tmp).
+ #
+ # Usage:
+ #
+ # UploadIO.new("file.txt", "text/plain")
+ # UploadIO.new(file_io, "text/plain", "file.txt")
+ #
+ attr_reader :content_type, :original_filename, :local_path, :io, :opts
+
+ def initialize(filename_or_io, content_type, filename = nil, opts = {})
+ io = filename_or_io
+ local_path = ""
+ if io.respond_to? :read
+ # in Ruby 1.9.2, StringIOs no longer respond to path
+ # (since they respond to :length, so we don't need their local path, see parts.rb:41)
+ local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
+ else
+ io = File.open(filename_or_io)
+ local_path = filename_or_io
+ end
+ filename ||= local_path
+
+ @content_type = content_type
+ @original_filename = File.basename(filename)
+ @local_path = local_path
+ @io = io
+ @opts = opts
+ end
+
+ def self.convert!(io, content_type, original_filename, local_path)
+ raise ArgumentError, "convert! has been removed. You must now wrap IOs using:\nUploadIO.new(filename_or_io, content_type, filename=nil)\nPlease update your code."
+ end
+
+ def method_missing(*args)
+ @io.send(*args)
+ end
+
+ def respond_to?(meth, include_all = false)
+ @io.respond_to?(meth, include_all) || super(meth, include_all)
+ end
+end
--- /dev/null
+require 'thread'
+require 'cgi'
+require 'set'
+require 'forwardable'
+
+# Public: This is the main namespace for Faraday. You can either use it to
+# create Faraday::Connection objects, or access it directly.
+#
+# Examples
+#
+# Faraday.get "http://faraday.com"
+#
+# conn = Faraday.new "http://faraday.com"
+# conn.get '/'
+#
+module Faraday
+ VERSION = "0.9.0"
+
+ class << self
+ # Public: Gets or sets the root path that Faraday is being loaded from.
+ # This is the root from where the libraries are auto-loaded from.
+ attr_accessor :root_path
+
+ # Public: Gets or sets the path that the Faraday libs are loaded from.
+ attr_accessor :lib_path
+
+ # Public: Gets or sets the Symbol key identifying a default Adapter to use
+ # for the default Faraday::Connection.
+ attr_reader :default_adapter
+
+ # Public: Sets the default Faraday::Connection for simple scripts that
+ # access the Faraday constant directly.
+ #
+ # Faraday.get "https://faraday.com"
+ attr_writer :default_connection
+
+ # Public: Sets the default options used when calling Faraday#new.
+ attr_writer :default_connection_options
+
+ # Public: Initializes a new Faraday::Connection.
+ #
+ # url - The optional String base URL to use as a prefix for all
+ # requests. Can also be the options Hash.
+ # options - The optional Hash used to configure this Faraday::Connection.
+ # Any of these values will be set on every request made, unless
+ # overridden for a specific request.
+ # :url - String base URL.
+ # :params - Hash of URI query unencoded key/value pairs.
+ # :headers - Hash of unencoded HTTP header key/value pairs.
+ # :request - Hash of request options.
+ # :ssl - Hash of SSL options.
+ # :proxy - Hash of Proxy options.
+ #
+ # Examples
+ #
+ # Faraday.new 'http://faraday.com'
+ #
+ # # http://faraday.com?page=1
+ # Faraday.new 'http://faraday.com', :params => {:page => 1}
+ #
+ # # same
+ #
+ # Faraday.new :url => 'http://faraday.com',
+ # :params => {:page => 1}
+ #
+ # Returns a Faraday::Connection.
+ def new(url = nil, options = nil)
+ block = block_given? ? Proc.new : nil
+ options = options ? default_connection_options.merge(options) : default_connection_options.dup
+ Faraday::Connection.new(url, options, &block)
+ end
+
+ # Internal: Requires internal Faraday libraries.
+ #
+ # *libs - One or more relative String names to Faraday classes.
+ #
+ # Returns nothing.
+ def require_libs(*libs)
+ libs.each do |lib|
+ require "#{lib_path}/#{lib}"
+ end
+ end
+
+ # Public: Updates default adapter while resetting
+ # #default_connection.
+ #
+ # Returns the new default_adapter.
+ def default_adapter=(adapter)
+ @default_connection = nil
+ @default_adapter = adapter
+ end
+
+ alias require_lib require_libs
+
+ private
+ # Internal: Proxies method calls on the Faraday constant to
+ # #default_connection.
+ def method_missing(name, *args, &block)
+ default_connection.send(name, *args, &block)
+ end
+ end
+
+ self.root_path = File.expand_path "..", __FILE__
+ self.lib_path = File.expand_path "../faraday", __FILE__
+ self.default_adapter = :net_http
+
+ # Gets the default connection used for simple scripts.
+ #
+ # Returns a Faraday::Connection, configured with the #default_adapter.
+ def self.default_connection
+ @default_connection ||= Connection.new
+ end
+
+ # Gets the default connection options used when calling Faraday#new.
+ #
+ # Returns a Faraday::ConnectionOptions.
+ def self.default_connection_options
+ @default_connection_options ||= ConnectionOptions.new
+ end
+
+ if (!defined?(RUBY_ENGINE) || "ruby" == RUBY_ENGINE) && RUBY_VERSION < '1.9'
+ begin
+ require 'system_timer'
+ Timer = SystemTimer
+ rescue LoadError
+ warn "Faraday: you may want to install system_timer for reliable timeouts"
+ end
+ end
+
+ unless const_defined? :Timer
+ require 'timeout'
+ Timer = Timeout
+ end
+
+ # Public: Adds the ability for other modules to register and lookup
+ # middleware classes.
+ module MiddlewareRegistry
+ # Public: Register middleware class(es) on the current module.
+ #
+ # mapping - A Hash mapping Symbol keys to classes. Classes can be expressed
+ # as fully qualified constant, or a Proc that will be lazily
+ # called to return the former.
+ #
+ # Examples
+ #
+ # module Faraday
+ # class Whatever
+ # # Middleware looked up by :foo returns Faraday::Whatever::Foo.
+ # register_middleware :foo => Foo
+ #
+ # # Middleware looked up by :bar returns Faraday::Whatever.const_get(:Bar)
+ # register_middleware :bar => :Bar
+ #
+ # # Middleware looked up by :baz requires 'baz' and returns Faraday::Whatever.const_get(:Baz)
+ # register_middleware :baz => [:Baz, 'baz']
+ # end
+ # end
+ #
+ # Returns nothing.
+ def register_middleware(autoload_path = nil, mapping = nil)
+ if mapping.nil?
+ mapping = autoload_path
+ autoload_path = nil
+ end
+ middleware_mutex do
+ @middleware_autoload_path = autoload_path if autoload_path
+ (@registered_middleware ||= {}).update(mapping)
+ end
+ end
+
+ # Public: Lookup middleware class with a registered Symbol shortcut.
+ #
+ # key - The Symbol key for the registered middleware.
+ #
+ # Examples
+ #
+ # module Faraday
+ # class Whatever
+ # register_middleware :foo => Foo
+ # end
+ # end
+ #
+ # Faraday::Whatever.lookup_middleware(:foo)
+ # # => Faraday::Whatever::Foo
+ #
+ # Returns a middleware Class.
+ def lookup_middleware(key)
+ load_middleware(key) ||
+ raise(Faraday::Error.new("#{key.inspect} is not registered on #{self}"))
+ end
+
+ def middleware_mutex(&block)
+ @middleware_mutex ||= begin
+ require 'monitor'
+ Monitor.new
+ end
+ @middleware_mutex.synchronize(&block)
+ end
+
+ def fetch_middleware(key)
+ defined?(@registered_middleware) && @registered_middleware[key]
+ end
+
+ def load_middleware(key)
+ value = fetch_middleware(key)
+ case value
+ when Module
+ value
+ when Symbol, String
+ middleware_mutex do
+ @registered_middleware[key] = const_get(value)
+ end
+ when Proc
+ middleware_mutex do
+ @registered_middleware[key] = value.call
+ end
+ when Array
+ middleware_mutex do
+ const, path = value
+ if root = @middleware_autoload_path
+ path = "#{root}/#{path}"
+ end
+ require(path)
+ @registered_middleware[key] = const
+ end
+ load_middleware(key)
+ end
+ end
+ end
+
+ def self.const_missing(name)
+ if name.to_sym == :Builder
+ warn "Faraday::Builder is now Faraday::RackBuilder."
+ const_set name, RackBuilder
+ else
+ super
+ end
+ end
+
+ require_libs "utils", "options", "connection", "rack_builder", "parameters",
+ "middleware", "adapter", "request", "response", "upload_io", "error"
+
+ if !ENV["FARADAY_NO_AUTOLOAD"]
+ require_lib 'autoload'
+ end
+end
+
+# not pulling in active-support JUST for this method. And I love this method.
+class Object
+ # The primary purpose of this method is to "tap into" a method chain,
+ # in order to perform operations on intermediate results within the chain.
+ #
+ # Examples
+ #
+ # (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
+ # tap { |x| puts "array: #{x.inspect}" }.
+ # select { |x| x%2 == 0 }.
+ # tap { |x| puts "evens: #{x.inspect}" }.
+ # map { |x| x*x }.
+ # tap { |x| puts "squares: #{x.inspect}" }
+ #
+ # Yields self.
+ # Returns self.
+ def tap
+ yield(self)
+ self
+ end unless Object.respond_to?(:tap)
+end
--- /dev/null
+module Faraday
+ # Public: This is a base class for all Faraday adapters. Adapters are
+ # responsible for fulfilling a Faraday request.
+ class Adapter < Middleware
+ CONTENT_LENGTH = 'Content-Length'.freeze
+
+ register_middleware File.expand_path('../adapter', __FILE__),
+ :test => [:Test, 'test'],
+ :net_http => [:NetHttp, 'net_http'],
+ :net_http_persistent => [:NetHttpPersistent, 'net_http_persistent'],
+ :typhoeus => [:Typhoeus, 'typhoeus'],
+ :patron => [:Patron, 'patron'],
+ :em_synchrony => [:EMSynchrony, 'em_synchrony'],
+ :em_http => [:EMHttp, 'em_http'],
+ :excon => [:Excon, 'excon'],
+ :rack => [:Rack, 'rack'],
+ :httpclient => [:HTTPClient, 'httpclient']
+
+ # Public: This module marks an Adapter as supporting parallel requests.
+ module Parallelism
+ attr_writer :supports_parallel
+ def supports_parallel?() @supports_parallel end
+
+ def inherited(subclass)
+ super
+ subclass.supports_parallel = self.supports_parallel?
+ end
+ end
+
+ extend Parallelism
+ self.supports_parallel = false
+
+ def call(env)
+ env.clear_body if env.needs_body?
+ end
+
+ def save_response(env, status, body, headers = nil)
+ env.status = status
+ env.body = body
+ env.response_headers = Utils::Headers.new.tap do |response_headers|
+ response_headers.update headers unless headers.nil?
+ yield(response_headers) if block_given?
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ # EventMachine adapter is useful for either asynchronous requests
+ # when in EM reactor loop or for making parallel requests in
+ # synchronous code.
+ class EMHttp < Faraday::Adapter
+ module Options
+ def connection_config(env)
+ options = {}
+ configure_proxy(options, env)
+ configure_timeout(options, env)
+ configure_socket(options, env)
+ configure_ssl(options, env)
+ options
+ end
+
+ def request_config(env)
+ options = {
+ :body => read_body(env),
+ :head => env[:request_headers],
+ # :keepalive => true,
+ # :file => 'path/to/file', # stream data off disk
+ }
+ configure_compression(options, env)
+ options
+ end
+
+ def read_body(env)
+ body = env[:body]
+ body.respond_to?(:read) ? body.read : body
+ end
+
+ def configure_proxy(options, env)
+ if proxy = request_options(env)[:proxy]
+ options[:proxy] = {
+ :host => proxy[:uri].host,
+ :port => proxy[:uri].port,
+ :authorization => [proxy[:user], proxy[:password]]
+ }
+ end
+ end
+
+ def configure_socket(options, env)
+ if bind = request_options(env)[:bind]
+ options[:bind] = {
+ :host => bind[:host],
+ :port => bind[:port]
+ }
+ end
+ end
+
+ def configure_ssl(options, env)
+ if env[:url].scheme == 'https' && env[:ssl]
+ options[:ssl] = {
+ :cert_chain_file => env[:ssl][:ca_file],
+ :verify_peer => env[:ssl].fetch(:verify, true)
+ }
+ end
+ end
+
+ def configure_timeout(options, env)
+ timeout, open_timeout = request_options(env).values_at(:timeout, :open_timeout)
+ options[:connect_timeout] = options[:inactivity_timeout] = timeout
+ options[:connect_timeout] = open_timeout if open_timeout
+ end
+
+ def configure_compression(options, env)
+ if env[:method] == :get and not options[:head].key? 'accept-encoding'
+ options[:head]['accept-encoding'] = 'gzip, compressed'
+ end
+ end
+
+ def request_options(env)
+ env[:request]
+ end
+ end
+
+ include Options
+
+ dependency 'em-http'
+
+ self.supports_parallel = true
+
+ def self.setup_parallel_manager(options = nil)
+ Manager.new
+ end
+
+ def call(env)
+ super
+ perform_request env
+ @app.call env
+ end
+
+ def perform_request(env)
+ if parallel?(env)
+ manager = env[:parallel_manager]
+ manager.add {
+ perform_single_request(env).
+ callback { env[:response].finish(env) }
+ }
+ else
+ unless EventMachine.reactor_running?
+ error = nil
+ # start EM, block until request is completed
+ EventMachine.run do
+ perform_single_request(env).
+ callback { EventMachine.stop }.
+ errback { |client|
+ error = error_message(client)
+ EventMachine.stop
+ }
+ end
+ raise_error(error) if error
+ else
+ # EM is running: instruct upstream that this is an async request
+ env[:parallel_manager] = true
+ perform_single_request(env).
+ callback { env[:response].finish(env) }.
+ errback {
+ # TODO: no way to communicate the error in async mode
+ raise NotImplementedError
+ }
+ end
+ end
+ rescue EventMachine::Connectify::CONNECTError => err
+ if err.message.include?("Proxy Authentication Required")
+ raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Error::ConnectionFailed, err
+ end
+ rescue => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise
+ end
+ end
+
+ # TODO: reuse the connection to support pipelining
+ def perform_single_request(env)
+ req = EventMachine::HttpRequest.new(env[:url], connection_config(env))
+ req.setup_request(env[:method], request_config(env)).callback { |client|
+ save_response(env, client.response_header.status, client.response) do |resp_headers|
+ client.response_header.each do |name, value|
+ resp_headers[name.to_sym] = value
+ end
+ end
+ }
+ end
+
+ def error_message(client)
+ client.error or "request failed"
+ end
+
+ def raise_error(msg)
+ errklass = Faraday::Error::ClientError
+ if msg == Errno::ETIMEDOUT
+ errklass = Faraday::Error::TimeoutError
+ msg = "request timed out"
+ elsif msg == Errno::ECONNREFUSED
+ errklass = Faraday::Error::ConnectionFailed
+ msg = "connection refused"
+ elsif msg == "connection closed by server"
+ errklass = Faraday::Error::ConnectionFailed
+ end
+ raise errklass, msg
+ end
+
+ def parallel?(env)
+ !!env[:parallel_manager]
+ end
+
+ # The parallel manager is designed to start an EventMachine loop
+ # and block until all registered requests have been completed.
+ class Manager
+ def initialize
+ reset
+ end
+
+ def reset
+ @registered_procs = []
+ @num_registered = 0
+ @num_succeeded = 0
+ @errors = []
+ @running = false
+ end
+
+ def running?() @running end
+
+ def add
+ if running?
+ perform_request { yield }
+ else
+ @registered_procs << Proc.new
+ end
+ @num_registered += 1
+ end
+
+ def run
+ if @num_registered > 0
+ @running = true
+ EventMachine.run do
+ @registered_procs.each do |proc|
+ perform_request(&proc)
+ end
+ end
+ if @errors.size > 0
+ raise Faraday::Error::ClientError, @errors.first || "connection failed"
+ end
+ end
+ ensure
+ reset
+ end
+
+ def perform_request
+ client = yield
+ client.callback { @num_succeeded += 1; check_finished }
+ client.errback { @errors << client.error; check_finished }
+ end
+
+ def check_finished
+ if @num_succeeded + @errors.size == @num_registered
+ EventMachine.stop
+ end
+ end
+ end
+ end
+ end
+end
+
+begin
+ require 'openssl'
+rescue LoadError
+ warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support"
+else
+ require 'faraday/adapter/em_http_ssl_patch'
+end if Faraday::Adapter::EMHttp.loaded?
--- /dev/null
+require 'openssl'
+require 'em-http'
+
+module EmHttpSslPatch
+ def ssl_verify_peer(cert_string)
+ cert = nil
+ begin
+ cert = OpenSSL::X509::Certificate.new(cert_string)
+ rescue OpenSSL::X509::CertificateError
+ return false
+ end
+
+ @last_seen_cert = cert
+
+ if certificate_store.verify(@last_seen_cert)
+ begin
+ certificate_store.add_cert(@last_seen_cert)
+ rescue OpenSSL::X509::StoreError => e
+ raise e unless e.message == 'cert already in hash table'
+ end
+ true
+ else
+ raise OpenSSL::SSL::SSLError.new(%(unable to verify the server certificate for "#{host}"))
+ end
+ end
+
+ def ssl_handshake_completed
+ return true unless verify_peer?
+
+ unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, host)
+ raise OpenSSL::SSL::SSLError.new(%(host "#{host}" does not match the server certificate))
+ else
+ true
+ end
+ end
+
+ def verify_peer?
+ parent.connopts.tls[:verify_peer]
+ end
+
+ def host
+ parent.connopts.host
+ end
+
+ def certificate_store
+ @certificate_store ||= begin
+ store = OpenSSL::X509::Store.new
+ store.set_default_paths
+ ca_file = parent.connopts.tls[:cert_chain_file]
+ store.add_file(ca_file) if ca_file
+ store
+ end
+ end
+end
+
+EventMachine::HttpStubConnection.send(:include, EmHttpSslPatch)
--- /dev/null
+require 'uri'
+
+module Faraday
+ class Adapter
+ class EMSynchrony < Faraday::Adapter
+ include EMHttp::Options
+
+ dependency do
+ require 'em-synchrony/em-http'
+ require 'em-synchrony/em-multi'
+ require 'fiber'
+ end
+
+ self.supports_parallel = true
+
+ def self.setup_parallel_manager(options = {})
+ ParallelManager.new
+ end
+
+ def call(env)
+ super
+ request = EventMachine::HttpRequest.new(Utils::URI(env[:url].to_s), connection_config(env))
+
+ http_method = env[:method].to_s.downcase.to_sym
+
+ # Queue requests for parallel execution.
+ if env[:parallel_manager]
+ env[:parallel_manager].add(request, http_method, request_config(env)) do |resp|
+ save_response(env, resp.response_header.status, resp.response) do |resp_headers|
+ resp.response_header.each do |name, value|
+ resp_headers[name.to_sym] = value
+ end
+ end
+
+ # Finalize the response object with values from `env`.
+ env[:response].finish(env)
+ end
+
+ # Execute single request.
+ else
+ client = nil
+ block = lambda { request.send(http_method, request_config(env)) }
+
+ if !EM.reactor_running?
+ EM.run do
+ Fiber.new {
+ client = block.call
+ EM.stop
+ }.resume
+ end
+ else
+ client = block.call
+ end
+
+ raise client.error if client.error
+
+ save_response(env, client.response_header.status, client.response) do |resp_headers|
+ client.response_header.each do |name, value|
+ resp_headers[name.to_sym] = value
+ end
+ end
+ end
+
+ @app.call env
+ rescue Errno::ECONNREFUSED
+ raise Error::ConnectionFailed, $!
+ rescue EventMachine::Connectify::CONNECTError => err
+ if err.message.include?("Proxy Authentication Required")
+ raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Error::ConnectionFailed, err
+ end
+ rescue => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise
+ end
+ end
+ end
+ end
+end
+
+require 'faraday/adapter/em_synchrony/parallel_manager'
+
+begin
+ require 'openssl'
+rescue LoadError
+ warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support"
+else
+ require 'faraday/adapter/em_http_ssl_patch'
+end if Faraday::Adapter::EMSynchrony.loaded?
--- /dev/null
+module Faraday
+ class Adapter
+ class EMSynchrony < Faraday::Adapter
+ class ParallelManager
+
+ # Add requests to queue. The `request` argument should be a
+ # `EM::HttpRequest` object.
+ def add(request, method, *args, &block)
+ queue << {
+ :request => request,
+ :method => method,
+ :args => args,
+ :block => block
+ }
+ end
+
+ # Run all requests on queue with `EM::Synchrony::Multi`, wrapping
+ # it in a reactor and fiber if needed.
+ def run
+ result = nil
+ if !EM.reactor_running?
+ EM.run {
+ Fiber.new do
+ result = perform
+ EM.stop
+ end.resume
+ }
+ else
+ result = perform
+ end
+ result
+ end
+
+
+ private
+
+ # The request queue.
+ def queue
+ @queue ||= []
+ end
+
+ # Main `EM::Synchrony::Multi` performer.
+ def perform
+ multi = ::EM::Synchrony::Multi.new
+
+ queue.each do |item|
+ method = "a#{item[:method]}".to_sym
+
+ req = item[:request].send(method, *item[:args])
+ req.callback(&item[:block])
+
+ req_name = "req_#{multi.requests.size}".to_sym
+ multi.add(req_name, req)
+ end
+
+ # Clear the queue, so parallel manager objects can be reused.
+ @queue = []
+
+ # Block fiber until all requests have returned.
+ multi.perform
+ end
+
+ end # ParallelManager
+ end # EMSynchrony
+ end # Adapter
+end # Faraday
--- /dev/null
+module Faraday
+ class Adapter
+ class Excon < Faraday::Adapter
+ dependency 'excon'
+
+ def initialize(app, connection_options = {})
+ @connection_options = connection_options
+ super(app)
+ end
+
+ def call(env)
+ super
+
+ opts = {}
+ if env[:url].scheme == 'https' && ssl = env[:ssl]
+ opts[:ssl_verify_peer] = !!ssl.fetch(:verify, true)
+ opts[:ssl_ca_path] = ssl[:ca_path] if ssl[:ca_path]
+ opts[:ssl_ca_file] = ssl[:ca_file] if ssl[:ca_file]
+ opts[:client_cert] = ssl[:client_cert] if ssl[:client_cert]
+ opts[:client_key] = ssl[:client_key] if ssl[:client_key]
+ opts[:certificate] = ssl[:certificate] if ssl[:certificate]
+ opts[:private_key] = ssl[:private_key] if ssl[:private_key]
+
+ # https://github.com/geemus/excon/issues/106
+ # https://github.com/jruby/jruby-ossl/issues/19
+ opts[:nonblock] = false
+ end
+
+ if ( req = env[:request] )
+ if req[:timeout]
+ opts[:read_timeout] = req[:timeout]
+ opts[:connect_timeout] = req[:timeout]
+ opts[:write_timeout] = req[:timeout]
+ end
+
+ if req[:open_timeout]
+ opts[:connect_timeout] = req[:open_timeout]
+ opts[:write_timeout] = req[:open_timeout]
+ end
+
+ if req[:proxy]
+ opts[:proxy] = {
+ :host => req[:proxy][:uri].host,
+ :port => req[:proxy][:uri].port,
+ :scheme => req[:proxy][:uri].scheme,
+ :user => req[:proxy][:user],
+ :password => req[:proxy][:password]
+ }
+ end
+ end
+
+ conn = ::Excon.new(env[:url].to_s, opts.merge(@connection_options))
+
+ resp = conn.request \
+ :method => env[:method].to_s.upcase,
+ :headers => env[:request_headers],
+ :body => read_body(env)
+
+ save_response(env, resp.status.to_i, resp.body, resp.headers)
+
+ @app.call env
+ rescue ::Excon::Errors::SocketError => err
+ if err.message =~ /\btimeout\b/
+ raise Error::TimeoutError, err
+ elsif err.message =~ /\bcertificate\b/
+ raise Faraday::SSLError, err
+ else
+ raise Error::ConnectionFailed, err
+ end
+ rescue ::Excon::Errors::Timeout => err
+ raise Error::TimeoutError, err
+ end
+
+ # TODO: support streaming requests
+ def read_body(env)
+ env[:body].respond_to?(:read) ? env[:body].read : env[:body]
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ class HTTPClient < Faraday::Adapter
+ dependency 'httpclient'
+
+ def client
+ @client ||= ::HTTPClient.new
+ end
+
+ def call(env)
+ super
+
+ if req = env[:request]
+ if proxy = req[:proxy]
+ configure_proxy proxy
+ end
+
+ if bind = req[:bind]
+ configure_socket bind
+ end
+
+ configure_timeouts req
+ end
+
+ if env[:url].scheme == 'https' && ssl = env[:ssl]
+ configure_ssl ssl
+ end
+
+ # TODO Don't stream yet.
+ # https://github.com/nahi/httpclient/pull/90
+ env[:body] = env[:body].read if env[:body].respond_to? :read
+
+ resp = client.request env[:method], env[:url],
+ :body => env[:body],
+ :header => env[:request_headers]
+
+ save_response env, resp.status, resp.body, resp.headers
+
+ @app.call env
+ rescue ::HTTPClient::TimeoutError
+ raise Faraday::Error::TimeoutError, $!
+ rescue ::HTTPClient::BadResponseError => err
+ if err.message.include?('status 407')
+ raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Faraday::Error::ClientError, $!
+ end
+ rescue Errno::ECONNREFUSED, EOFError
+ raise Faraday::Error::ConnectionFailed, $!
+ rescue => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise
+ end
+ end
+
+ def configure_socket(bind)
+ client.socket_local.host = bind[:host]
+ client.socket_local.port = bind[:port]
+ end
+
+ def configure_proxy(proxy)
+ client.proxy = proxy[:uri]
+ if proxy[:user] && proxy[:password]
+ client.set_proxy_auth proxy[:user], proxy[:password]
+ end
+ end
+
+ def configure_ssl(ssl)
+ ssl_config = client.ssl_config
+
+ ssl_config.add_trust_ca ssl[:ca_file] if ssl[:ca_file]
+ ssl_config.add_trust_ca ssl[:ca_path] if ssl[:ca_path]
+ ssl_config.cert_store = ssl[:cert_store] if ssl[:cert_store]
+ ssl_config.client_cert = ssl[:client_cert] if ssl[:client_cert]
+ ssl_config.client_key = ssl[:client_key] if ssl[:client_key]
+ ssl_config.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
+ ssl_config.verify_mode = ssl_verify_mode(ssl)
+ end
+
+ def configure_timeouts(req)
+ if req[:timeout]
+ client.connect_timeout = req[:timeout]
+ client.receive_timeout = req[:timeout]
+ client.send_timeout = req[:timeout]
+ end
+
+ if req[:open_timeout]
+ client.connect_timeout = req[:open_timeout]
+ client.send_timeout = req[:open_timeout]
+ end
+ end
+
+ def ssl_verify_mode(ssl)
+ ssl[:verify_mode] || begin
+ if ssl.fetch(:verify, true)
+ OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ else
+ OpenSSL::SSL::VERIFY_NONE
+ end
+ end
+ end
+ end
+ end
+end
--- /dev/null
+begin
+ require 'net/https'
+rescue LoadError
+ warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support"
+ require 'net/http'
+end
+require 'zlib'
+
+module Faraday
+ class Adapter
+ class NetHttp < Faraday::Adapter
+ NET_HTTP_EXCEPTIONS = [
+ EOFError,
+ Errno::ECONNABORTED,
+ Errno::ECONNREFUSED,
+ Errno::ECONNRESET,
+ Errno::EHOSTUNREACH,
+ Errno::EINVAL,
+ Errno::ENETUNREACH,
+ Net::HTTPBadResponse,
+ Net::HTTPHeaderSyntaxError,
+ Net::ProtocolError,
+ SocketError,
+ Zlib::GzipFile::Error,
+ ]
+
+ NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
+
+ def call(env)
+ super
+ http = net_http_connection(env)
+ configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
+
+ req = env[:request]
+ http.read_timeout = http.open_timeout = req[:timeout] if req[:timeout]
+ http.open_timeout = req[:open_timeout] if req[:open_timeout]
+
+ begin
+ http_response = perform_request(http, env)
+ rescue *NET_HTTP_EXCEPTIONS => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise Error::ConnectionFailed, err
+ end
+ end
+
+ save_response(env, http_response.code.to_i, http_response.body || '') do |response_headers|
+ http_response.each_header do |key, value|
+ response_headers[key] = value
+ end
+ end
+
+ @app.call env
+ rescue Timeout::Error => err
+ raise Faraday::Error::TimeoutError, err
+ end
+
+ def create_request(env)
+ request = Net::HTTPGenericRequest.new \
+ env[:method].to_s.upcase, # request method
+ !!env[:body], # is there request body
+ :head != env[:method], # is there response body
+ env[:url].request_uri, # request uri path
+ env[:request_headers] # request headers
+
+ if env[:body].respond_to?(:read)
+ request.body_stream = env[:body]
+ else
+ request.body = env[:body]
+ end
+ request
+ end
+
+ def perform_request(http, env)
+ if :get == env[:method] and !env[:body]
+ # prefer `get` to `request` because the former handles gzip (ruby 1.9)
+ http.get env[:url].request_uri, env[:request_headers]
+ else
+ http.request create_request(env)
+ end
+ end
+
+ def net_http_connection(env)
+ if proxy = env[:request][:proxy]
+ Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:user], proxy[:password])
+ else
+ Net::HTTP
+ end.new(env[:url].host, env[:url].port)
+ end
+
+ def configure_ssl(http, ssl)
+ http.use_ssl = true
+ http.verify_mode = ssl_verify_mode(ssl)
+ http.cert_store = ssl_cert_store(ssl)
+
+ http.cert = ssl[:client_cert] if ssl[:client_cert]
+ http.key = ssl[:client_key] if ssl[:client_key]
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
+ http.ca_path = ssl[:ca_path] if ssl[:ca_path]
+ http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
+ http.ssl_version = ssl[:version] if ssl[:version]
+ end
+
+ def ssl_cert_store(ssl)
+ return ssl[:cert_store] if ssl[:cert_store]
+ # Use the default cert store by default, i.e. system ca certs
+ cert_store = OpenSSL::X509::Store.new
+ cert_store.set_default_paths
+ cert_store
+ end
+
+ def ssl_verify_mode(ssl)
+ ssl[:verify_mode] || begin
+ if ssl.fetch(:verify, true)
+ OpenSSL::SSL::VERIFY_PEER
+ else
+ OpenSSL::SSL::VERIFY_NONE
+ end
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# Rely on autoloading instead of explicit require; helps avoid the "already
+# initialized constant" warning on Ruby 1.8.7 when NetHttp is refereced below.
+# require 'faraday/adapter/net_http'
+
+module Faraday
+ class Adapter
+ # Experimental adapter for net-http-persistent
+ class NetHttpPersistent < NetHttp
+ dependency 'net/http/persistent'
+
+ def net_http_connection(env)
+ if proxy = env[:request][:proxy]
+ proxy_uri = ::URI::HTTP === proxy[:uri] ? proxy[:uri].dup : ::URI.parse(proxy[:uri].to_s)
+ proxy_uri.user = proxy_uri.password = nil
+ # awful patch for net-http-persistent 2.8 not unescaping user/password
+ (class << proxy_uri; self; end).class_eval do
+ define_method(:user) { proxy[:user] }
+ define_method(:password) { proxy[:password] }
+ end if proxy[:user]
+ end
+ Net::HTTP::Persistent.new 'Faraday', proxy_uri
+ end
+
+ def perform_request(http, env)
+ http.request env[:url], create_request(env)
+ rescue Net::HTTP::Persistent::Error => error
+ if error.message.include? 'Timeout'
+ raise Faraday::Error::TimeoutError, error
+ elsif error.message.include? 'connection refused'
+ raise Faraday::Error::ConnectionFailed, error
+ else
+ raise
+ end
+ end
+
+ def configure_ssl(http, ssl)
+ http.verify_mode = ssl_verify_mode(ssl)
+ http.cert_store = ssl_cert_store(ssl)
+
+ http.certificate = ssl[:client_cert] if ssl[:client_cert]
+ http.private_key = ssl[:client_key] if ssl[:client_key]
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
+ http.ssl_version = ssl[:version] if ssl[:version]
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ class Patron < Faraday::Adapter
+ dependency 'patron'
+
+ def initialize(app, &block)
+ super(app)
+ @block = block
+ end
+
+ def call(env)
+ super
+
+ # TODO: support streaming requests
+ env[:body] = env[:body].read if env[:body].respond_to? :read
+
+ session = @session ||= create_session
+
+ if req = env[:request]
+ session.timeout = session.connect_timeout = req[:timeout] if req[:timeout]
+ session.connect_timeout = req[:open_timeout] if req[:open_timeout]
+
+ if proxy = req[:proxy]
+ proxy_uri = proxy[:uri].dup
+ proxy_uri.user = proxy[:user] && Utils.escape(proxy[:user]).gsub('+', '%20')
+ proxy_uri.password = proxy[:password] && Utils.escape(proxy[:password]).gsub('+', '%20')
+ session.proxy = proxy_uri.to_s
+ end
+ end
+
+ response = begin
+ data = env[:body] ? env[:body].to_s : nil
+ session.request(env[:method], env[:url].to_s, env[:request_headers], :data => data)
+ rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed
+ raise Error::ConnectionFailed, $!
+ end
+
+ save_response(env, response.status, response.body, response.headers)
+
+ @app.call env
+ rescue ::Patron::TimeoutError => err
+ if err.message == "Connection time-out"
+ raise Faraday::Error::ConnectionFailed, err
+ else
+ raise Faraday::Error::TimeoutError, err
+ end
+ rescue ::Patron::Error => err
+ if err.message.include?("code 407")
+ raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Error::ConnectionFailed, err
+ end
+ end
+
+ if loaded? && defined?(::Patron::Request::VALID_ACTIONS)
+ # HAX: helps but doesn't work completely
+ # https://github.com/toland/patron/issues/34
+ ::Patron::Request::VALID_ACTIONS.tap do |actions|
+ actions << :patch unless actions.include? :patch
+ actions << :options unless actions.include? :options
+ end
+ end
+
+ def create_session
+ session = ::Patron::Session.new
+ session.insecure = true
+ @block.call(session) if @block
+ session
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ # Sends requests to a Rack app.
+ #
+ # Examples
+ #
+ # class MyRackApp
+ # def call(env)
+ # [200, {'Content-Type' => 'text/html'}, ["hello world"]]
+ # end
+ # end
+ #
+ # Faraday.new do |conn|
+ # conn.adapter :rack, MyRackApp.new
+ # end
+ class Rack < Faraday::Adapter
+ dependency 'rack/test'
+
+ # not prefixed with "HTTP_"
+ SPECIAL_HEADERS = %w[ CONTENT_LENGTH CONTENT_TYPE ]
+
+ def initialize(faraday_app, rack_app)
+ super(faraday_app)
+ mock_session = ::Rack::MockSession.new(rack_app)
+ @session = ::Rack::Test::Session.new(mock_session)
+ end
+
+ def call(env)
+ super
+ rack_env = {
+ :method => env[:method],
+ :input => env[:body].respond_to?(:read) ? env[:body].read : env[:body],
+ 'rack.url_scheme' => env[:url].scheme
+ }
+
+ env[:request_headers].each do |name, value|
+ name = name.upcase.tr('-', '_')
+ name = "HTTP_#{name}" unless SPECIAL_HEADERS.include? name
+ rack_env[name] = value
+ end if env[:request_headers]
+
+ timeout = env[:request][:timeout] || env[:request][:open_timeout]
+ response = if timeout
+ Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
+ else
+ execute_request(env, rack_env)
+ end
+
+ save_response(env, response.status, response.body, response.headers)
+ @app.call env
+ end
+
+ def execute_request(env, rack_env)
+ @session.request(env[:url].to_s, rack_env)
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ # test = Faraday::Connection.new do
+ # use Faraday::Adapter::Test do |stub|
+ # stub.get '/nigiri/sake.json' do
+ # [200, {}, 'hi world']
+ # end
+ # end
+ # end
+ #
+ # resp = test.get '/nigiri/sake.json'
+ # resp.body # => 'hi world'
+ #
+ class Test < Faraday::Adapter
+ attr_accessor :stubs
+
+ class Stubs
+ class NotFound < StandardError
+ end
+
+ def initialize
+ # {:get => [Stub, Stub]}
+ @stack, @consumed = {}, {}
+ yield(self) if block_given?
+ end
+
+ def empty?
+ @stack.empty?
+ end
+
+ def match(request_method, path, headers, body)
+ return false if !@stack.key?(request_method)
+ stack = @stack[request_method]
+ consumed = (@consumed[request_method] ||= [])
+
+ if stub = matches?(stack, path, headers, body)
+ consumed << stack.delete(stub)
+ stub
+ else
+ matches?(consumed, path, headers, body)
+ end
+ end
+
+ def get(path, headers = {}, &block)
+ new_stub(:get, path, headers, &block)
+ end
+
+ def head(path, headers = {}, &block)
+ new_stub(:head, path, headers, &block)
+ end
+
+ def post(path, body=nil, headers = {}, &block)
+ new_stub(:post, path, headers, body, &block)
+ end
+
+ def put(path, body=nil, headers = {}, &block)
+ new_stub(:put, path, headers, body, &block)
+ end
+
+ def patch(path, body=nil, headers = {}, &block)
+ new_stub(:patch, path, headers, body, &block)
+ end
+
+ def delete(path, headers = {}, &block)
+ new_stub(:delete, path, headers, &block)
+ end
+
+ def options(path, headers = {}, &block)
+ new_stub(:options, path, headers, &block)
+ end
+
+ # Raises an error if any of the stubbed calls have not been made.
+ def verify_stubbed_calls
+ failed_stubs = []
+ @stack.each do |method, stubs|
+ unless stubs.size == 0
+ failed_stubs.concat(stubs.map {|stub|
+ "Expected #{method} #{stub}."
+ })
+ end
+ end
+ raise failed_stubs.join(" ") unless failed_stubs.size == 0
+ end
+
+ protected
+
+ def new_stub(request_method, path, headers = {}, body=nil, &block)
+ normalized_path = Faraday::Utils.normalize_path(path)
+ (@stack[request_method] ||= []) << Stub.new(normalized_path, headers, body, block)
+ end
+
+ def matches?(stack, path, headers, body)
+ stack.detect { |stub| stub.matches?(path, headers, body) }
+ end
+ end
+
+ class Stub < Struct.new(:path, :params, :headers, :body, :block)
+ def initialize(full, headers, body, block)
+ path, query = full.split('?')
+ params = query ?
+ Faraday::Utils.parse_nested_query(query) :
+ {}
+ super(path, params, headers, body, block)
+ end
+
+ def matches?(request_uri, request_headers, request_body)
+ request_path, request_query = request_uri.split('?')
+ request_params = request_query ?
+ Faraday::Utils.parse_nested_query(request_query) :
+ {}
+ request_path == path &&
+ params_match?(request_params) &&
+ (body.to_s.size.zero? || request_body == body) &&
+ headers_match?(request_headers)
+ end
+
+ def params_match?(request_params)
+ params.keys.all? do |key|
+ request_params[key] == params[key]
+ end
+ end
+
+ def headers_match?(request_headers)
+ headers.keys.all? do |key|
+ request_headers[key] == headers[key]
+ end
+ end
+
+ def to_s
+ "#{path} #{body}"
+ end
+ end
+
+ def initialize(app, stubs=nil, &block)
+ super(app)
+ @stubs = stubs || Stubs.new
+ configure(&block) if block
+ end
+
+ def configure
+ yield(stubs)
+ end
+
+ def call(env)
+ super
+ normalized_path = Faraday::Utils.normalize_path(env[:url])
+ params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder
+
+ if stub = stubs.match(env[:method], normalized_path, env.request_headers, env[:body])
+ env[:params] = (query = env[:url].query) ?
+ params_encoder.decode(query) :
+ {}
+ status, headers, body = stub.block.call(env)
+ save_response(env, status, body, headers)
+ else
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
+ end
+ @app.call(env)
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ class Typhoeus < Faraday::Adapter
+ self.supports_parallel = true
+
+ def self.setup_parallel_manager(options = {})
+ options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
+ end
+
+ dependency 'typhoeus'
+
+ def call(env)
+ super
+ perform_request env
+ @app.call env
+ end
+
+ def perform_request(env)
+ read_body env
+
+ hydra = env[:parallel_manager] || self.class.setup_parallel_manager
+ hydra.queue request(env)
+ hydra.run unless parallel?(env)
+ rescue Errno::ECONNREFUSED
+ raise Error::ConnectionFailed, $!
+ end
+
+ # TODO: support streaming requests
+ def read_body(env)
+ env[:body] = env[:body].read if env[:body].respond_to? :read
+ end
+
+ def request(env)
+ method = env[:method]
+ # For some reason, prevents Typhoeus from using "100-continue".
+ # We want this because Webrick 1.3.1 can't seem to handle it w/ PUT.
+ method = method.to_s.upcase if method == :put
+
+ req = ::Typhoeus::Request.new env[:url].to_s,
+ :method => method,
+ :body => env[:body],
+ :headers => env[:request_headers],
+ :disable_ssl_peer_verification => (env[:ssl] && env[:ssl].disable?)
+
+ configure_ssl req, env
+ configure_proxy req, env
+ configure_timeout req, env
+ configure_socket req, env
+
+ req.on_complete do |resp|
+ if resp.timed_out?
+ if parallel?(env)
+ # TODO: error callback in async mode
+ else
+ raise Faraday::Error::TimeoutError, "request timed out"
+ end
+ end
+
+ case resp.curl_return_code
+ when 0
+ # everything OK
+ when 7
+ raise Error::ConnectionFailed, resp.curl_error_message
+ when 60
+ raise Faraday::SSLError, resp.curl_error_message
+ else
+ raise Error::ClientError, resp.curl_error_message
+ end
+
+ save_response(env, resp.code, resp.body) do |response_headers|
+ response_headers.parse resp.headers
+ end
+ # in async mode, :response is initialized at this point
+ env[:response].finish(env) if parallel?(env)
+ end
+
+ req
+ end
+
+ def configure_ssl(req, env)
+ ssl = env[:ssl]
+
+ req.ssl_version = ssl[:version] if ssl[:version]
+ req.ssl_cert = ssl[:client_cert] if ssl[:client_cert]
+ req.ssl_key = ssl[:client_key] if ssl[:client_key]
+ req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file]
+ req.ssl_capath = ssl[:ca_path] if ssl[:ca_path]
+ end
+
+ def configure_proxy(req, env)
+ proxy = request_options(env)[:proxy]
+ return unless proxy
+
+ req.proxy = "#{proxy[:uri].host}:#{proxy[:uri].port}"
+
+ if proxy[:user] && proxy[:password]
+ req.proxy_username = proxy[:user]
+ req.proxy_password = proxy[:password]
+ end
+ end
+
+ def configure_timeout(req, env)
+ env_req = request_options(env)
+ req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
+ req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
+ end
+
+ def configure_socket(req, env)
+ if bind = request_options(env)[:bind]
+ req.interface = bind[:host]
+ end
+ end
+
+ def request_options(env)
+ env[:request]
+ end
+
+ def parallel?(env)
+ !!env[:parallel_manager]
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Internal: Adds the ability for other modules to manage autoloadable
+ # constants.
+ module AutoloadHelper
+ # Internal: Registers the constants to be auto loaded.
+ #
+ # prefix - The String require prefix. If the path is inside Faraday, then
+ # it will be prefixed with the root path of this loaded Faraday
+ # version.
+ # options - Hash of Symbol => String library names.
+ #
+ # Examples.
+ #
+ # Faraday.autoload_all 'faraday/foo',
+ # :Bar => 'bar'
+ #
+ # # requires faraday/foo/bar to load Faraday::Bar.
+ # Faraday::Bar
+ #
+ #
+ # Returns nothing.
+ def autoload_all(prefix, options)
+ if prefix =~ /^faraday(\/|$)/i
+ prefix = File.join(Faraday.root_path, prefix)
+ end
+ options.each do |const_name, path|
+ autoload const_name, File.join(prefix, path)
+ end
+ end
+
+ # Internal: Loads each autoloaded constant. If thread safety is a concern,
+ # wrap this in a Mutex.
+ #
+ # Returns nothing.
+ def load_autoloaded_constants
+ constants.each do |const|
+ const_get(const) if autoload?(const)
+ end
+ end
+
+ # Internal: Filters the module's contents with those that have been already
+ # autoloaded.
+ #
+ # Returns an Array of Class/Module objects.
+ def all_loaded_constants
+ constants.map { |c| const_get(c) }.
+ select { |a| a.respond_to?(:loaded?) && a.loaded? }
+ end
+ end
+
+ class Adapter
+ extend AutoloadHelper
+ autoload_all 'faraday/adapter',
+ :NetHttp => 'net_http',
+ :NetHttpPersistent => 'net_http_persistent',
+ :Typhoeus => 'typhoeus',
+ :EMSynchrony => 'em_synchrony',
+ :EMHttp => 'em_http',
+ :Patron => 'patron',
+ :Excon => 'excon',
+ :Test => 'test',
+ :Rack => 'rack',
+ :HTTPClient => 'httpclient'
+ end
+
+ class Request
+ extend AutoloadHelper
+ autoload_all 'faraday/request',
+ :UrlEncoded => 'url_encoded',
+ :Multipart => 'multipart',
+ :Retry => 'retry',
+ :Timeout => 'timeout',
+ :Authorization => 'authorization',
+ :BasicAuthentication => 'basic_authentication',
+ :TokenAuthentication => 'token_authentication',
+ :Instrumentation => 'instrumentation'
+ end
+
+ class Response
+ extend AutoloadHelper
+ autoload_all 'faraday/response',
+ :RaiseError => 'raise_error',
+ :Logger => 'logger'
+ end
+end
--- /dev/null
+module Faraday
+ # Public: Connection objects manage the default properties and the middleware
+ # stack for fulfilling an HTTP request.
+ #
+ # Examples
+ #
+ # conn = Faraday::Connection.new 'http://sushi.com'
+ #
+ # # GET http://sushi.com/nigiri
+ # conn.get 'nigiri'
+ # # => #<Faraday::Response>
+ #
+ class Connection
+ # A Set of allowed HTTP verbs.
+ METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
+
+ # Public: Returns a Hash of URI query unencoded key/value pairs.
+ attr_reader :params
+
+ # Public: Returns a Hash of unencoded HTTP header key/value pairs.
+ attr_reader :headers
+
+ # Public: Returns a URI with the prefix used for all requests from this
+ # Connection. This includes a default host name, scheme, port, and path.
+ attr_reader :url_prefix
+
+ # Public: Returns the Faraday::Builder for this Connection.
+ attr_reader :builder
+
+ # Public: Returns a Hash of the request options.
+ attr_reader :options
+
+ # Public: Returns a Hash of the SSL options.
+ attr_reader :ssl
+
+ # Public: Returns the parallel manager for this Connection.
+ attr_reader :parallel_manager
+
+ # Public: Sets the default parallel manager for this connection.
+ attr_writer :default_parallel_manager
+
+ # Public: Initializes a new Faraday::Connection.
+ #
+ # url - URI or String base URL to use as a prefix for all
+ # requests (optional).
+ # options - Hash or Faraday::ConnectionOptions.
+ # :url - URI or String base URL (default: "http:/").
+ # :params - Hash of URI query unencoded key/value pairs.
+ # :headers - Hash of unencoded HTTP header key/value pairs.
+ # :request - Hash of request options.
+ # :ssl - Hash of SSL options.
+ # :proxy - URI, String or Hash of HTTP proxy options
+ # (default: "http_proxy" environment variable).
+ # :uri - URI or String
+ # :user - String (optional)
+ # :password - String (optional)
+ def initialize(url = nil, options = nil)
+ if url.is_a?(Hash)
+ options = ConnectionOptions.from(url)
+ url = options.url
+ else
+ options = ConnectionOptions.from(options)
+ end
+
+ @parallel_manager = nil
+ @headers = Utils::Headers.new
+ @params = Utils::ParamsHash.new
+ @options = options.request
+ @ssl = options.ssl
+ @default_parallel_manager = options.parallel_manager
+
+ @builder = options.builder || begin
+ # pass an empty block to Builder so it doesn't assume default middleware
+ options.new_builder(block_given? ? Proc.new { |b| } : nil)
+ end
+
+ self.url_prefix = url || 'http:/'
+
+ @params.update(options.params) if options.params
+ @headers.update(options.headers) if options.headers
+
+ @proxy = nil
+ proxy(options.fetch(:proxy) {
+ uri = ENV['http_proxy']
+ if uri && !uri.empty?
+ uri = 'http://' + uri if uri !~ /^http/i
+ uri
+ end
+ })
+
+ yield(self) if block_given?
+
+ @headers[:user_agent] ||= "Faraday v#{VERSION}"
+ end
+
+ # Public: Sets the Hash of URI query unencoded key/value pairs.
+ def params=(hash)
+ @params.replace hash
+ end
+
+ # Public: Sets the Hash of unencoded HTTP header key/value pairs.
+ def headers=(hash)
+ @headers.replace hash
+ end
+
+ extend Forwardable
+
+ def_delegators :builder, :build, :use, :request, :response, :adapter, :app
+
+ # Public: Makes an HTTP request without a body.
+ #
+ # url - The optional String base URL to use as a prefix for all
+ # requests. Can also be the options Hash.
+ # params - Hash of URI query unencoded key/value pairs.
+ # headers - Hash of unencoded HTTP header key/value pairs.
+ #
+ # Examples
+ #
+ # conn.get '/items', {:page => 1}, :accept => 'application/json'
+ # conn.head '/items/1'
+ #
+ # # ElasticSearch example sending a body with GET.
+ # conn.get '/twitter/tweet/_search' do |req|
+ # req.headers[:content_type] = 'application/json'
+ # req.params[:routing] = 'kimchy'
+ # req.body = JSON.generate(:query => {...})
+ # end
+ #
+ # Yields a Faraday::Response for further request customizations.
+ # Returns a Faraday::Response.
+ #
+ # Signature
+ #
+ # <verb>(url = nil, params = nil, headers = nil)
+ #
+ # verb - An HTTP verb: get, head, or delete.
+ %w[get head delete].each do |method|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(url = nil, params = nil, headers = nil)
+ run_request(:#{method}, url, nil, headers) { |request|
+ request.params.update(params) if params
+ yield(request) if block_given?
+ }
+ end
+ RUBY
+ end
+
+ # Public: Makes an HTTP request with a body.
+ #
+ # url - The optional String base URL to use as a prefix for all
+ # requests. Can also be the options Hash.
+ # body - The String body for the request.
+ # headers - Hash of unencoded HTTP header key/value pairs.
+ #
+ # Examples
+ #
+ # conn.post '/items', data, :content_type => 'application/json'
+ #
+ # # Simple ElasticSearch indexing sample.
+ # conn.post '/twitter/tweet' do |req|
+ # req.headers[:content_type] = 'application/json'
+ # req.params[:routing] = 'kimchy'
+ # req.body = JSON.generate(:user => 'kimchy', ...)
+ # end
+ #
+ # Yields a Faraday::Response for further request customizations.
+ # Returns a Faraday::Response.
+ #
+ # Signature
+ #
+ # <verb>(url = nil, body = nil, headers = nil)
+ #
+ # verb - An HTTP verb: post, put, or patch.
+ %w[post put patch].each do |method|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(url = nil, body = nil, headers = nil, &block)
+ run_request(:#{method}, url, body, headers, &block)
+ end
+ RUBY
+ end
+
+ # Public: Sets up the Authorization header with these credentials, encoded
+ # with base64.
+ #
+ # login - The authentication login.
+ # pass - The authentication password.
+ #
+ # Examples
+ #
+ # conn.basic_auth 'Aladdin', 'open sesame'
+ # conn.headers['Authorization']
+ # # => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ #
+ # Returns nothing.
+ def basic_auth(login, pass)
+ set_authorization_header(:basic_auth, login, pass)
+ end
+
+ # Public: Sets up the Authorization header with the given token.
+ #
+ # token - The String token.
+ # options - Optional Hash of extra token options.
+ #
+ # Examples
+ #
+ # conn.token_auth 'abcdef', :foo => 'bar'
+ # conn.headers['Authorization']
+ # # => "Token token=\"abcdef\",
+ # foo=\"bar\""
+ #
+ # Returns nothing.
+ def token_auth(token, options = nil)
+ set_authorization_header(:token_auth, token, options)
+ end
+
+ # Public: Sets up a custom Authorization header.
+ #
+ # type - The String authorization type.
+ # token - The String or Hash token. A String value is taken literally, and
+ # a Hash is encoded into comma separated key/value pairs.
+ #
+ # Examples
+ #
+ # conn.authorization :Bearer, 'mF_9.B5f-4.1JqM'
+ # conn.headers['Authorization']
+ # # => "Bearer mF_9.B5f-4.1JqM"
+ #
+ # conn.authorization :Token, :token => 'abcdef', :foo => 'bar'
+ # conn.headers['Authorization']
+ # # => "Token token=\"abcdef\",
+ # foo=\"bar\""
+ #
+ # Returns nothing.
+ def authorization(type, token)
+ set_authorization_header(:authorization, type, token)
+ end
+
+ # Internal: Traverse the middleware stack in search of a
+ # parallel-capable adapter.
+ #
+ # Yields in case of not found.
+ #
+ # Returns a parallel manager or nil if not found.
+ def default_parallel_manager
+ @default_parallel_manager ||= begin
+ handler = @builder.handlers.detect do |h|
+ h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
+ end
+
+ if handler
+ handler.klass.setup_parallel_manager
+ elsif block_given?
+ yield
+ end
+ end
+ end
+
+ # Public: Determine if this Faraday::Connection can make parallel requests.
+ #
+ # Returns true or false.
+ def in_parallel?
+ !!@parallel_manager
+ end
+
+ # Public: Sets up the parallel manager to make a set of requests.
+ #
+ # manager - The parallel manager that this Connection's Adapter uses.
+ #
+ # Yields a block to execute multiple requests.
+ # Returns nothing.
+ def in_parallel(manager = nil)
+ @parallel_manager = manager || default_parallel_manager {
+ warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack"
+ warn caller[2,10].join("\n")
+ nil
+ }
+ yield
+ @parallel_manager && @parallel_manager.run
+ ensure
+ @parallel_manager = nil
+ end
+
+ # Public: Gets or Sets the Hash proxy options.
+ def proxy(arg = nil)
+ return @proxy if arg.nil?
+ @proxy = ProxyOptions.from(arg)
+ end
+
+ def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
+ def_delegator :url_prefix, :path, :path_prefix
+
+ # Public: Parses the giving url with URI and stores the individual
+ # components in this connection. These components serve as defaults for
+ # requests made by this connection.
+ #
+ # url - A String or URI.
+ #
+ # Examples
+ #
+ # conn = Faraday::Connection.new { ... }
+ # conn.url_prefix = "https://sushi.com/api"
+ # conn.scheme # => https
+ # conn.path_prefix # => "/api"
+ #
+ # conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
+ #
+ # Returns the parsed URI from teh given input..
+ def url_prefix=(url, encoder = nil)
+ uri = @url_prefix = Utils.URI(url)
+ self.path_prefix = uri.path
+
+ params.merge_query(uri.query, encoder)
+ uri.query = nil
+
+ with_uri_credentials(uri) do |user, password|
+ basic_auth user, password
+ uri.user = uri.password = nil
+ end
+
+ uri
+ end
+
+ # Public: Sets the path prefix and ensures that it always has a leading
+ # slash.
+ #
+ # value - A String.
+ #
+ # Returns the new String path prefix.
+ def path_prefix=(value)
+ url_prefix.path = if value
+ value = '/' + value unless value[0,1] == '/'
+ value
+ end
+ end
+
+ # Public: Takes a relative url for a request and combines it with the defaults
+ # set on the connection instance.
+ #
+ # conn = Faraday::Connection.new { ... }
+ # conn.url_prefix = "https://sushi.com/api?token=abc"
+ # conn.scheme # => https
+ # conn.path_prefix # => "/api"
+ #
+ # conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
+ # conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
+ #
+ def build_url(url = nil, extra_params = nil)
+ uri = build_exclusive_url(url)
+
+ query_values = params.dup.merge_query(uri.query, options.params_encoder)
+ query_values.update extra_params if extra_params
+ uri.query = query_values.empty? ? nil : query_values.to_query(options.params_encoder)
+
+ uri
+ end
+
+ # Builds and runs the Faraday::Request.
+ #
+ # method - The Symbol HTTP method.
+ # url - The String or URI to access.
+ # body - The String body
+ # headers - Hash of unencoded HTTP header key/value pairs.
+ #
+ # Returns a Faraday::Response.
+ def run_request(method, url, body, headers)
+ if !METHODS.include?(method)
+ raise ArgumentError, "unknown http method: #{method}"
+ end
+
+ request = build_request(method) do |req|
+ req.url(url) if url
+ req.headers.update(headers) if headers
+ req.body = body if body
+ yield(req) if block_given?
+ end
+
+ builder.build_response(self, request)
+ end
+
+ # Creates and configures the request object.
+ #
+ # Returns the new Request.
+ def build_request(method)
+ Request.create(method) do |req|
+ req.params = self.params.dup
+ req.headers = self.headers.dup
+ req.options = self.options.merge(:proxy => self.proxy)
+ yield(req) if block_given?
+ end
+ end
+
+ # Internal: Build an absolute URL based on url_prefix.
+ #
+ # url - A String or URI-like object
+ # params - A Faraday::Utils::ParamsHash to replace the query values
+ # of the resulting url (default: nil).
+ #
+ # Returns the resulting URI instance.
+ def build_exclusive_url(url = nil, params = nil)
+ url = nil if url.respond_to?(:empty?) and url.empty?
+ base = url_prefix
+ if url and base.path and base.path !~ /\/$/
+ base = base.dup
+ base.path = base.path + '/' # ensure trailing slash
+ end
+ uri = url ? base + url : base
+ uri.query = params.to_query(options.params_encoder) if params
+ uri.query = nil if uri.query and uri.query.empty?
+ uri
+ end
+
+ # Internal: Creates a duplicate of this Faraday::Connection.
+ #
+ # Returns a Faraday::Connection.
+ def dup
+ self.class.new(build_exclusive_url, :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
+ end
+
+ # Internal: Yields username and password extracted from a URI if they both exist.
+ def with_uri_credentials(uri)
+ if uri.user and uri.password
+ yield(Utils.unescape(uri.user), Utils.unescape(uri.password))
+ end
+ end
+
+ def set_authorization_header(header_type, *args)
+ header = Faraday::Request.lookup_middleware(header_type).
+ header(*args)
+ headers[Faraday::Request::Authorization::KEY] = header
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Error < StandardError; end
+ class MissingDependency < Error; end
+
+ class ClientError < Error
+ attr_reader :response
+
+ def initialize(ex, response = nil)
+ @wrapped_exception = nil
+ @response = response
+
+ if ex.respond_to?(:backtrace)
+ super(ex.message)
+ @wrapped_exception = ex
+ elsif ex.respond_to?(:each_key)
+ super("the server responded with status #{ex[:status]}")
+ @response = ex
+ else
+ super(ex.to_s)
+ end
+ end
+
+ def backtrace
+ if @wrapped_exception
+ @wrapped_exception.backtrace
+ else
+ super
+ end
+ end
+
+ def inspect
+ %(#<#{self.class}>)
+ end
+ end
+
+ class ConnectionFailed < ClientError; end
+ class ResourceNotFound < ClientError; end
+ class ParsingError < ClientError; end
+
+ class TimeoutError < ClientError
+ def initialize(ex = nil)
+ super(ex || "timeout")
+ end
+ end
+
+ class SSLError < ClientError
+ end
+
+ [:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
+ :ParsingError, :TimeoutError, :SSLError].each do |const|
+ Error.const_set(const, Faraday.const_get(const))
+ end
+end
--- /dev/null
+module Faraday
+ class Middleware
+ extend MiddlewareRegistry
+
+ class << self
+ attr_accessor :load_error
+ private :load_error=
+ end
+
+ self.load_error = nil
+
+ # Executes a block which should try to require and reference dependent libraries
+ def self.dependency(lib = nil)
+ lib ? require(lib) : yield
+ rescue LoadError, NameError => error
+ self.load_error = error
+ end
+
+ def self.new(*)
+ raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
+ super
+ end
+
+ def self.loaded?
+ load_error.nil?
+ end
+
+ def self.inherited(subclass)
+ super
+ subclass.send(:load_error=, self.load_error)
+ end
+
+ def initialize(app = nil)
+ @app = app
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Subclasses Struct with some special helpers for converting from a Hash to
+ # a Struct.
+ class Options < Struct
+ # Public
+ def self.from(value)
+ value ? new.update(value) : new
+ end
+
+ # Public
+ def each
+ return to_enum(:each) unless block_given?
+ members.each do |key|
+ yield(key.to_sym, send(key))
+ end
+ end
+
+ # Public
+ def update(obj)
+ obj.each do |key, value|
+ if sub_options = self.class.options_for(key)
+ value = sub_options.from(value) if value
+ elsif Hash === value
+ hash = {}
+ value.each do |hash_key, hash_value|
+ hash[hash_key] = hash_value
+ end
+ value = hash
+ end
+
+ self.send("#{key}=", value) unless value.nil?
+ end
+ self
+ end
+
+ alias merge! update
+
+ # Public
+ def delete(key)
+ value = send(key)
+ send("#{key}=", nil)
+ value
+ end
+
+ # Public
+ def clear
+ members.each { |member| delete(member) }
+ end
+
+ # Public
+ def merge(value)
+ dup.update(value)
+ end
+
+ # Public
+ def fetch(key, *args)
+ unless symbolized_key_set.include?(key.to_sym)
+ key_setter = "#{key}="
+ if args.size > 0
+ send(key_setter, args.first)
+ elsif block_given?
+ send(key_setter, Proc.new.call(key))
+ else
+ raise self.class.fetch_error_class, "key not found: #{key.inspect}"
+ end
+ end
+ send(key)
+ end
+
+ # Public
+ def values_at(*keys)
+ keys.map { |key| send(key) }
+ end
+
+ # Public
+ def keys
+ members.reject { |member| send(member).nil? }
+ end
+
+ # Public
+ def empty?
+ keys.empty?
+ end
+
+ # Public
+ def each_key
+ return to_enum(:each_key) unless block_given?
+ keys.each do |key|
+ yield(key)
+ end
+ end
+
+ # Public
+ def key?(key)
+ keys.include?(key)
+ end
+
+ alias has_key? key?
+
+ # Public
+ def each_value
+ return to_enum(:each_value) unless block_given?
+ values.each do |value|
+ yield(value)
+ end
+ end
+
+ # Public
+ def value?(value)
+ values.include?(value)
+ end
+
+ alias has_value? value?
+
+ # Public
+ def to_hash
+ hash = {}
+ members.each do |key|
+ value = send(key)
+ hash[key.to_sym] = value unless value.nil?
+ end
+ hash
+ end
+
+ # Internal
+ def inspect
+ values = []
+ members.each do |member|
+ value = send(member)
+ values << "#{member}=#{value.inspect}" if value
+ end
+ values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
+
+ %(#<#{self.class}#{values}>)
+ end
+
+ # Internal
+ def self.options(mapping)
+ attribute_options.update(mapping)
+ end
+
+ # Internal
+ def self.options_for(key)
+ attribute_options[key]
+ end
+
+ # Internal
+ def self.attribute_options
+ @attribute_options ||= {}
+ end
+
+ def self.memoized(key)
+ memoized_attributes[key.to_sym] = Proc.new
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{key}() self[:#{key}]; end
+ RUBY
+ end
+
+ def self.memoized_attributes
+ @memoized_attributes ||= {}
+ end
+
+ def [](key)
+ key = key.to_sym
+ if method = self.class.memoized_attributes[key]
+ super(key) || (self[key] = instance_eval(&method))
+ else
+ super
+ end
+ end
+
+ def symbolized_key_set
+ @symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym })
+ end
+
+ def self.inherited(subclass)
+ super
+ subclass.attribute_options.update(attribute_options)
+ subclass.memoized_attributes.update(memoized_attributes)
+ end
+
+ def self.fetch_error_class
+ @fetch_error_class ||= if Object.const_defined?(:KeyError)
+ ::KeyError
+ else
+ ::IndexError
+ end
+ end
+ end
+
+ class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
+ :timeout, :open_timeout, :boundary,
+ :oauth)
+
+ def []=(key, value)
+ if key && key.to_sym == :proxy
+ super(key, value ? ProxyOptions.from(value) : nil)
+ else
+ super(key, value)
+ end
+ end
+ end
+
+ class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
+ :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth, :version)
+
+ def verify?
+ verify != false
+ end
+
+ def disable?
+ !verify?
+ end
+ end
+
+ class ProxyOptions < Options.new(:uri, :user, :password)
+ extend Forwardable
+ def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
+
+ def self.from(value)
+ case value
+ when String
+ value = {:uri => Utils.URI(value)}
+ when URI
+ value = {:uri => value}
+ when Hash, Options
+ if uri = value.delete(:uri)
+ value[:uri] = Utils.URI(uri)
+ end
+ end
+ super(value)
+ end
+
+ memoized(:user) { uri.user && Utils.unescape(uri.user) }
+ memoized(:password) { uri.password && Utils.unescape(uri.password) }
+ end
+
+ class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
+ :parallel_manager, :params, :headers, :builder_class)
+
+ options :request => RequestOptions, :ssl => SSLOptions
+
+ memoized(:request) { self.class.options_for(:request).new }
+
+ memoized(:ssl) { self.class.options_for(:ssl).new }
+
+ memoized(:builder_class) { RackBuilder }
+
+ def new_builder(block)
+ builder_class.new(&block)
+ end
+ end
+
+ class Env < Options.new(:method, :body, :url, :request, :request_headers,
+ :ssl, :parallel_manager, :params, :response, :response_headers, :status)
+
+ ContentLength = 'Content-Length'.freeze
+ StatusesWithoutBody = Set.new [204, 304]
+ SuccessfulStatuses = 200..299
+
+ # A Set of HTTP verbs that typically send a body. If no body is set for
+ # these requests, the Content-Length header is set to 0.
+ MethodsWithBodies = Set.new [:post, :put, :patch, :options]
+
+ options :request => RequestOptions,
+ :request_headers => Utils::Headers, :response_headers => Utils::Headers
+
+ extend Forwardable
+
+ def_delegators :request, :params_encoder
+
+ # Public
+ def [](key)
+ if in_member_set?(key)
+ super(key)
+ else
+ custom_members[key]
+ end
+ end
+
+ # Public
+ def []=(key, value)
+ if in_member_set?(key)
+ super(key, value)
+ else
+ custom_members[key] = value
+ end
+ end
+
+ # Public
+ def success?
+ SuccessfulStatuses.include?(status)
+ end
+
+ # Public
+ def needs_body?
+ !body && MethodsWithBodies.include?(method)
+ end
+
+ # Public
+ def clear_body
+ request_headers[ContentLength] = '0'
+ self.body = ''
+ end
+
+ # Public
+ def parse_body?
+ !StatusesWithoutBody.include?(status)
+ end
+
+ # Public
+ def parallel?
+ !!parallel_manager
+ end
+
+ def inspect
+ attrs = [nil]
+ members.each do |mem|
+ if value = send(mem)
+ attrs << "@#{mem}=#{value.inspect}"
+ end
+ end
+ if !custom_members.empty?
+ attrs << "@custom=#{custom_members.inspect}"
+ end
+ %(#<#{self.class}#{attrs.join(" ")}>)
+ end
+
+ # Internal
+ def custom_members
+ @custom_members ||= {}
+ end
+
+ # Internal
+ if members.first.is_a?(Symbol)
+ def in_member_set?(key)
+ self.class.member_set.include?(key.to_sym)
+ end
+ else
+ def in_member_set?(key)
+ self.class.member_set.include?(key.to_s)
+ end
+ end
+
+ # Internal
+ def self.member_set
+ @member_set ||= Set.new(members)
+ end
+ end
+end
--- /dev/null
+module Faraday
+ module NestedParamsEncoder
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
+
+ def self.escape(s)
+ return s.to_s.gsub(ESCAPE_RE) {
+ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
+ }.tr(' ', '+')
+ end
+
+ def self.unescape(s)
+ CGI.unescape(s.to_s)
+ end
+
+ def self.encode(params)
+ return nil if params == nil
+
+ if !params.is_a?(Array)
+ if !params.respond_to?(:to_hash)
+ raise TypeError,
+ "Can't convert #{params.class} into Hash."
+ end
+ params = params.to_hash
+ params = params.map do |key, value|
+ key = key.to_s if key.kind_of?(Symbol)
+ [key, value]
+ end
+ # Useful default for OAuth and caching.
+ # Only to be used for non-Array inputs. Arrays should preserve order.
+ params.sort!
+ end
+
+ # Helper lambda
+ to_query = lambda do |parent, value|
+ if value.is_a?(Hash)
+ value = value.map do |key, val|
+ key = escape(key)
+ [key, val]
+ end
+ value.sort!
+ buffer = ""
+ value.each do |key, val|
+ new_parent = "#{parent}%5B#{key}%5D"
+ buffer << "#{to_query.call(new_parent, val)}&"
+ end
+ return buffer.chop
+ elsif value.is_a?(Array)
+ buffer = ""
+ value.each_with_index do |val, i|
+ new_parent = "#{parent}%5B%5D"
+ buffer << "#{to_query.call(new_parent, val)}&"
+ end
+ return buffer.chop
+ else
+ encoded_value = escape(value)
+ return "#{parent}=#{encoded_value}"
+ end
+ end
+
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
+ buffer = ''
+ params.each do |parent, value|
+ encoded_parent = escape(parent)
+ buffer << "#{to_query.call(encoded_parent, value)}&"
+ end
+ return buffer.chop
+ end
+
+ def self.decode(query)
+ return nil if query == nil
+ # Recursive helper lambda
+ dehash = lambda do |hash|
+ hash.each do |(key, value)|
+ if value.kind_of?(Hash)
+ hash[key] = dehash.call(value)
+ end
+ end
+ # Numeric keys implies an array
+ if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
+ hash.sort.inject([]) do |accu, (_, value)|
+ accu << value; accu
+ end
+ else
+ hash
+ end
+ end
+
+ empty_accumulator = {}
+ return ((query.split('&').map do |pair|
+ pair.split('=', 2) if pair && !pair.empty?
+ end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
+ key = unescape(key)
+ if value.kind_of?(String)
+ value = unescape(value.gsub(/\+/, ' '))
+ end
+
+ array_notation = !!(key =~ /\[\]$/)
+ subkeys = key.split(/[\[\]]+/)
+ current_hash = accu
+ for i in 0...(subkeys.size - 1)
+ subkey = subkeys[i]
+ current_hash[subkey] = {} unless current_hash[subkey]
+ current_hash = current_hash[subkey]
+ end
+ if array_notation
+ current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
+ current_hash[subkeys.last] << value
+ else
+ current_hash[subkeys.last] = value
+ end
+ accu
+ end).inject(empty_accumulator.dup) do |accu, (key, value)|
+ accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
+ accu
+ end
+ end
+ end
+
+ module FlatParamsEncoder
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
+
+ def self.escape(s)
+ return s.to_s.gsub(ESCAPE_RE) {
+ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
+ }.tr(' ', '+')
+ end
+
+ def self.unescape(s)
+ CGI.unescape(s.to_s)
+ end
+
+ def self.encode(params)
+ return nil if params == nil
+
+ if !params.is_a?(Array)
+ if !params.respond_to?(:to_hash)
+ raise TypeError,
+ "Can't convert #{params.class} into Hash."
+ end
+ params = params.to_hash
+ params = params.map do |key, value|
+ key = key.to_s if key.kind_of?(Symbol)
+ [key, value]
+ end
+ # Useful default for OAuth and caching.
+ # Only to be used for non-Array inputs. Arrays should preserve order.
+ params.sort!
+ end
+
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
+ buffer = ''
+ params.each do |key, value|
+ encoded_key = escape(key)
+ value = value.to_s if value == true || value == false
+ if value == nil
+ buffer << "#{encoded_key}&"
+ elsif value.kind_of?(Array)
+ value.each do |sub_value|
+ encoded_value = escape(sub_value)
+ buffer << "#{encoded_key}=#{encoded_value}&"
+ end
+ else
+ encoded_value = escape(value)
+ buffer << "#{encoded_key}=#{encoded_value}&"
+ end
+ end
+ return buffer.chop
+ end
+
+ def self.decode(query)
+ empty_accumulator = {}
+ return nil if query == nil
+ split_query = (query.split('&').map do |pair|
+ pair.split('=', 2) if pair && !pair.empty?
+ end).compact
+ return split_query.inject(empty_accumulator.dup) do |accu, pair|
+ pair[0] = unescape(pair[0])
+ pair[1] = true if pair[1].nil?
+ if pair[1].respond_to?(:to_str)
+ pair[1] = unescape(pair[1].to_str.gsub(/\+/, " "))
+ end
+ if accu[pair[0]].kind_of?(Array)
+ accu[pair[0]] << pair[1]
+ elsif accu[pair[0]]
+ accu[pair[0]] = [accu[pair[0]], pair[1]]
+ else
+ accu[pair[0]] = pair[1]
+ end
+ accu
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # A Builder that processes requests into responses by passing through an inner
+ # middleware stack (heavily inspired by Rack).
+ #
+ # Faraday::Connection.new(:url => 'http://sushi.com') do |builder|
+ # builder.request :url_encoded # Faraday::Request::UrlEncoded
+ # builder.adapter :net_http # Faraday::Adapter::NetHttp
+ # end
+ class RackBuilder
+ attr_accessor :handlers
+
+ # Error raised when trying to modify the stack after calling `lock!`
+ class StackLocked < RuntimeError; end
+
+ # borrowed from ActiveSupport::Dependencies::Reference &
+ # ActionDispatch::MiddlewareStack::Middleware
+ class Handler
+ @@constants_mutex = Mutex.new
+ @@constants = Hash.new { |h, k|
+ value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
+ @@constants_mutex.synchronize { h[k] = value }
+ }
+
+ attr_reader :name
+
+ def initialize(klass, *args, &block)
+ @name = klass.to_s
+ if klass.respond_to?(:name)
+ @@constants_mutex.synchronize { @@constants[@name] = klass }
+ end
+ @args, @block = args, block
+ end
+
+ def klass() @@constants[@name] end
+ def inspect() @name end
+
+ def ==(other)
+ if other.is_a? Handler
+ self.name == other.name
+ elsif other.respond_to? :name
+ klass == other
+ else
+ @name == other.to_s
+ end
+ end
+
+ def build(app)
+ klass.new(app, *@args, &@block)
+ end
+ end
+
+ def initialize(handlers = [])
+ @handlers = handlers
+ if block_given?
+ build(&Proc.new)
+ elsif @handlers.empty?
+ # default stack, if nothing else is configured
+ self.request :url_encoded
+ self.adapter Faraday.default_adapter
+ end
+ end
+
+ def build(options = {})
+ raise_if_locked
+ @handlers.clear unless options[:keep]
+ yield(self) if block_given?
+ end
+
+ def [](idx)
+ @handlers[idx]
+ end
+
+ # Locks the middleware stack to ensure no further modifications are possible.
+ def lock!
+ @handlers.freeze
+ end
+
+ def locked?
+ @handlers.frozen?
+ end
+
+ def use(klass, *args, &block)
+ if klass.is_a? Symbol
+ use_symbol(Faraday::Middleware, klass, *args, &block)
+ else
+ raise_if_locked
+ @handlers << self.class::Handler.new(klass, *args, &block)
+ end
+ end
+
+ def request(key, *args, &block)
+ use_symbol(Faraday::Request, key, *args, &block)
+ end
+
+ def response(key, *args, &block)
+ use_symbol(Faraday::Response, key, *args, &block)
+ end
+
+ def adapter(key, *args, &block)
+ use_symbol(Faraday::Adapter, key, *args, &block)
+ end
+
+ ## methods to push onto the various positions in the stack:
+
+ def insert(index, *args, &block)
+ raise_if_locked
+ index = assert_index(index)
+ handler = self.class::Handler.new(*args, &block)
+ @handlers.insert(index, handler)
+ end
+
+ alias_method :insert_before, :insert
+
+ def insert_after(index, *args, &block)
+ index = assert_index(index)
+ insert(index + 1, *args, &block)
+ end
+
+ def swap(index, *args, &block)
+ raise_if_locked
+ index = assert_index(index)
+ @handlers.delete_at(index)
+ insert(index, *args, &block)
+ end
+
+ def delete(handler)
+ raise_if_locked
+ @handlers.delete(handler)
+ end
+
+ # Processes a Request into a Response by passing it through this Builder's
+ # middleware stack.
+ #
+ # connection - Faraday::Connection
+ # request - Faraday::Request
+ #
+ # Returns a Faraday::Response.
+ def build_response(connection, request)
+ app.call(build_env(connection, request))
+ end
+
+ # The "rack app" wrapped in middleware. All requests are sent here.
+ #
+ # The builder is responsible for creating the app object. After this,
+ # the builder gets locked to ensure no further modifications are made
+ # to the middleware stack.
+ #
+ # Returns an object that responds to `call` and returns a Response.
+ def app
+ @app ||= begin
+ lock!
+ to_app(lambda { |env|
+ response = Response.new
+ response.finish(env) unless env.parallel?
+ env.response = response
+ })
+ end
+ end
+
+ def to_app(inner_app)
+ # last added handler is the deepest and thus closest to the inner app
+ @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
+ end
+
+ def ==(other)
+ other.is_a?(self.class) && @handlers == other.handlers
+ end
+
+ def dup
+ self.class.new(@handlers.dup)
+ end
+
+ # ENV Keys
+ # :method - a symbolized request method (:get, :post)
+ # :body - the request body that will eventually be converted to a string.
+ # :url - URI instance for the current request.
+ # :status - HTTP response status code
+ # :request_headers - hash of HTTP Headers to be sent to the server
+ # :response_headers - Hash of HTTP headers from the server
+ # :parallel_manager - sent if the connection is in parallel mode
+ # :request - Hash of options for configuring the request.
+ # :timeout - open/read timeout Integer in seconds
+ # :open_timeout - read timeout Integer in seconds
+ # :proxy - Hash of proxy options
+ # :uri - Proxy Server URI
+ # :user - Proxy server username
+ # :password - Proxy server password
+ # :ssl - Hash of options for configuring SSL requests.
+ def build_env(connection, request)
+ Env.new(request.method, request.body,
+ connection.build_exclusive_url(request.path, request.params),
+ request.options, request.headers, connection.ssl,
+ connection.parallel_manager)
+ end
+
+ private
+
+ def raise_if_locked
+ raise StackLocked, "can't modify middleware stack after making a request" if locked?
+ end
+
+ def use_symbol(mod, key, *args, &block)
+ use(mod.lookup_middleware(key), *args, &block)
+ end
+
+ def assert_index(index)
+ idx = index.is_a?(Integer) ? index : @handlers.index(index)
+ raise "No such handler: #{index.inspect}" unless idx
+ idx
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Used to setup urls, params, headers, and the request body in a sane manner.
+ #
+ # @connection.post do |req|
+ # req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
+ # req.headers['b'] = '2' # Header
+ # req.params['c'] = '3' # GET Param
+ # req['b'] = '2' # also Header
+ # req.body = 'abc'
+ # end
+ #
+ class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
+ extend MiddlewareRegistry
+
+ register_middleware File.expand_path('../request', __FILE__),
+ :url_encoded => [:UrlEncoded, 'url_encoded'],
+ :multipart => [:Multipart, 'multipart'],
+ :retry => [:Retry, 'retry'],
+ :authorization => [:Authorization, 'authorization'],
+ :basic_auth => [:BasicAuthentication, 'basic_authentication'],
+ :token_auth => [:TokenAuthentication, 'token_authentication'],
+ :instrumentation => [:Instrumentation, 'instrumentation']
+
+ def self.create(request_method)
+ new(request_method).tap do |request|
+ yield(request) if block_given?
+ end
+ end
+
+ # Public: Replace params, preserving the existing hash type
+ def params=(hash)
+ if params
+ params.replace hash
+ else
+ super
+ end
+ end
+
+ # Public: Replace request headers, preserving the existing hash type
+ def headers=(hash)
+ if headers
+ headers.replace hash
+ else
+ super
+ end
+ end
+
+ def url(path, params = nil)
+ if path.respond_to? :query
+ if query = path.query
+ path = path.dup
+ path.query = nil
+ end
+ else
+ path, query = path.split('?', 2)
+ end
+ self.path = path
+ self.params.merge_query query, options.params_encoder
+ self.params.update(params) if params
+ end
+
+ def [](key)
+ headers[key]
+ end
+
+ def []=(key, value)
+ headers[key] = value
+ end
+
+ # ENV Keys
+ # :method - a symbolized request method (:get, :post)
+ # :body - the request body that will eventually be converted to a string.
+ # :url - URI instance for the current request.
+ # :status - HTTP response status code
+ # :request_headers - hash of HTTP Headers to be sent to the server
+ # :response_headers - Hash of HTTP headers from the server
+ # :parallel_manager - sent if the connection is in parallel mode
+ # :request - Hash of options for configuring the request.
+ # :timeout - open/read timeout Integer in seconds
+ # :open_timeout - read timeout Integer in seconds
+ # :proxy - Hash of proxy options
+ # :uri - Proxy Server URI
+ # :user - Proxy server username
+ # :password - Proxy server password
+ # :ssl - Hash of options for configuring SSL requests.
+ def to_env(connection)
+ Env.new(method, body, connection.build_exclusive_url(path, params),
+ options, headers, connection.ssl, connection.parallel_manager)
+ end
+ end
+end
+
--- /dev/null
+module Faraday
+ class Request::Authorization < Faraday::Middleware
+ KEY = "Authorization".freeze unless defined? KEY
+
+ # Public
+ def self.header(type, token)
+ case token
+ when String, Symbol
+ "#{type} #{token}"
+ when Hash
+ build_hash(type.to_s, token)
+ else
+ raise ArgumentError, "Can't build an Authorization #{type} header from #{token.inspect}"
+ end
+ end
+
+ # Internal
+ def self.build_hash(type, hash)
+ offset = KEY.size + type.size + 3
+ comma = ",\n#{' ' * offset}"
+ values = []
+ hash.each do |key, value|
+ values << "#{key}=#{value.to_s.inspect}"
+ end
+ "#{type} #{values * comma}"
+ end
+
+ def initialize(app, type, token)
+ @header_value = self.class.header(type, token)
+ super(app)
+ end
+
+ # Public
+ def call(env)
+ unless env.request_headers[KEY]
+ env.request_headers[KEY] = @header_value
+ end
+ @app.call(env)
+ end
+ end
+end
+
--- /dev/null
+require 'base64'
+
+module Faraday
+ class Request::BasicAuthentication < Request.load_middleware(:authorization)
+ # Public
+ def self.header(login, pass)
+ value = Base64.encode64([login, pass].join(':'))
+ value.gsub!("\n", '')
+ super(:Basic, value)
+ end
+ end
+end
+
--- /dev/null
+module Faraday
+ class Request::Instrumentation < Faraday::Middleware
+ class Options < Faraday::Options.new(:name, :instrumenter)
+ def name
+ self[:name] ||= 'request.faraday'
+ end
+
+ def instrumenter
+ self[:instrumenter] ||= ActiveSupport::Notifications
+ end
+ end
+
+ # Public: Instruments requests using Active Support.
+ #
+ # Measures time spent only for synchronous requests.
+ #
+ # Examples
+ #
+ # ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env|
+ # url = env[:url]
+ # http_method = env[:method].to_s.upcase
+ # duration = ends - starts
+ # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
+ # end
+ def initialize(app, options = nil)
+ super(app)
+ @name, @instrumenter = Options.from(options).values_at(:name, :instrumenter)
+ end
+
+ def call(env)
+ @instrumenter.instrument(@name, env) do
+ @app.call(env)
+ end
+ end
+ end
+end
--- /dev/null
+require File.expand_path("../url_encoded", __FILE__)
+
+module Faraday
+ class Request::Multipart < Request::UrlEncoded
+ self.mime_type = 'multipart/form-data'.freeze
+ DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze unless defined? DEFAULT_BOUNDARY
+
+ def call(env)
+ match_content_type(env) do |params|
+ env.request.boundary ||= DEFAULT_BOUNDARY
+ env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}"
+ env.body = create_multipart(env, params)
+ end
+ @app.call env
+ end
+
+ def process_request?(env)
+ type = request_type(env)
+ env.body.respond_to?(:each_key) and !env.body.empty? and (
+ (type.empty? and has_multipart?(env.body)) or
+ type == self.class.mime_type
+ )
+ end
+
+ def has_multipart?(obj)
+ # string is an enum in 1.8, returning list of itself
+ if obj.respond_to?(:each) && !obj.is_a?(String)
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
+ return true if (val.respond_to?(:content_type) || has_multipart?(val))
+ end
+ end
+ false
+ end
+
+ def create_multipart(env, params)
+ boundary = env.request.boundary
+ parts = process_params(params) do |key, value|
+ Faraday::Parts::Part.new(boundary, key, value)
+ end
+ parts << Faraday::Parts::EpiloguePart.new(boundary)
+
+ body = Faraday::CompositeReadIO.new(parts)
+ env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
+ return body
+ end
+
+ def process_params(params, prefix = nil, pieces = nil, &block)
+ params.inject(pieces || []) do |all, (key, value)|
+ key = "#{prefix}[#{key}]" if prefix
+
+ case value
+ when Array
+ values = value.inject([]) { |a,v| a << [nil, v] }
+ process_params(values, key, all, &block)
+ when Hash
+ process_params(value, key, all, &block)
+ else
+ all << block.call(key, value)
+ end
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Catches exceptions and retries each request a limited number of times.
+ #
+ # By default, it retries 2 times and handles only timeout exceptions. It can
+ # be configured with an arbitrary number of retries, a list of exceptions to
+ # handle, a retry interval, a percentage of randomness to add to the retry
+ # interval, and a backoff factor.
+ #
+ # Examples
+ #
+ # Faraday.new do |conn|
+ # conn.request :retry, max: 2, interval: 0.05,
+ # interval_randomness: 0.5, backoff_factor: 2
+ # exceptions: [CustomException, 'Timeout::Error']
+ # conn.adapter ...
+ # end
+ #
+ # This example will result in a first interval that is random between 0.05 and 0.075 and a second
+ # interval that is random between 0.1 and 0.15
+ #
+ class Request::Retry < Faraday::Middleware
+
+ IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
+
+ class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor, :exceptions, :retry_if)
+ DEFAULT_CHECK = lambda { |env,exception| false }
+
+ def self.from(value)
+ if Fixnum === value
+ new(value)
+ else
+ super(value)
+ end
+ end
+
+ def max
+ (self[:max] ||= 2).to_i
+ end
+
+ def interval
+ (self[:interval] ||= 0).to_f
+ end
+
+ def interval_randomness
+ (self[:interval_randomness] ||= 0).to_i
+ end
+
+ def backoff_factor
+ (self[:backoff_factor] ||= 1).to_f
+ end
+
+ def exceptions
+ Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
+ Error::TimeoutError])
+ end
+
+ def retry_if
+ self[:retry_if] ||= DEFAULT_CHECK
+ end
+
+ end
+
+ # Public: Initialize middleware
+ #
+ # Options:
+ # max - Maximum number of retries (default: 2)
+ # interval - Pause in seconds between retries (default: 0)
+ # interval_randomness - The maximum random interval amount expressed
+ # as a float between 0 and 1 to use in addition to the
+ # interval. (default: 0)
+ # backoff_factor - The amount to multiple each successive retry's
+ # interval amount by in order to provide backoff
+ # (default: 1)
+ # exceptions - The list of exceptions to handle. Exceptions can be
+ # given as Class, Module, or String. (default:
+ # [Errno::ETIMEDOUT, Timeout::Error,
+ # Error::TimeoutError])
+ # retry_if - block that will receive the env object and the exception raised
+ # and should decide if the code should retry still the action or
+ # not independent of the retry count. This would be useful
+ # if the exception produced is non-recoverable or if the
+ # the HTTP method called is not idempotent.
+ # (defaults to return false)
+ def initialize(app, options = nil)
+ super(app)
+ @options = Options.from(options)
+ @errmatch = build_exception_matcher(@options.exceptions)
+ end
+
+ def sleep_amount(retries)
+ retry_index = @options.max - retries
+ current_interval = @options.interval * (@options.backoff_factor ** retry_index)
+ random_interval = rand * @options.interval_randomness.to_f * @options.interval
+ current_interval + random_interval
+ end
+
+ def call(env)
+ retries = @options.max
+ request_body = env[:body]
+ begin
+ env[:body] = request_body # after failure env[:body] is set to the response body
+ @app.call(env)
+ rescue @errmatch => exception
+ if retries > 0 && retry_request?(env, exception)
+ retries -= 1
+ sleep sleep_amount(retries + 1)
+ retry
+ end
+ raise
+ end
+ end
+
+ # Private: construct an exception matcher object.
+ #
+ # An exception matcher for the rescue clause can usually be any object that
+ # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
+ def build_exception_matcher(exceptions)
+ matcher = Module.new
+ (class << matcher; self; end).class_eval do
+ define_method(:===) do |error|
+ exceptions.any? do |ex|
+ if ex.is_a? Module
+ error.is_a? ex
+ else
+ error.class.to_s == ex.to_s
+ end
+ end
+ end
+ end
+ matcher
+ end
+
+ private
+
+ def retry_request?(env, exception)
+ IDEMPOTENT_METHODS.include?(env[:method]) || @options.retry_if.call(env, exception)
+ end
+
+ end
+end
--- /dev/null
+module Faraday
+ class Request::TokenAuthentication < Request.load_middleware(:authorization)
+ # Public
+ def self.header(token, options = nil)
+ options ||= {}
+ options[:token] = token
+ super(:Token, options)
+ end
+
+ def initialize(app, token, options = nil)
+ super(app, token, options)
+ end
+ end
+end
+
--- /dev/null
+module Faraday
+ class Request::UrlEncoded < Faraday::Middleware
+ CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
+
+ class << self
+ attr_accessor :mime_type
+ end
+ self.mime_type = 'application/x-www-form-urlencoded'.freeze
+
+ def call(env)
+ match_content_type(env) do |data|
+ params = Faraday::Utils::ParamsHash[data]
+ env.body = params.to_query(env.params_encoder)
+ end
+ @app.call env
+ end
+
+ def match_content_type(env)
+ if process_request?(env)
+ env.request_headers[CONTENT_TYPE] ||= self.class.mime_type
+ yield(env.body) unless env.body.respond_to?(:to_str)
+ end
+ end
+
+ def process_request?(env)
+ type = request_type(env)
+ env.body and (type.empty? or type == self.class.mime_type)
+ end
+
+ def request_type(env)
+ type = env.request_headers[CONTENT_TYPE].to_s
+ type = type.split(';', 2).first if type.index(';')
+ type
+ end
+ end
+end
--- /dev/null
+require 'forwardable'
+
+module Faraday
+ class Response
+ # Used for simple response middleware.
+ class Middleware < Faraday::Middleware
+ def call(env)
+ @app.call(env).on_complete do |environment|
+ on_complete(environment)
+ end
+ end
+
+ # Override this to modify the environment after the response has finished.
+ # Calls the `parse` method if defined
+ def on_complete(env)
+ env.body = parse(env.body) if respond_to?(:parse) && env.parse_body?
+ end
+ end
+
+ extend Forwardable
+ extend MiddlewareRegistry
+
+ register_middleware File.expand_path('../response', __FILE__),
+ :raise_error => [:RaiseError, 'raise_error'],
+ :logger => [:Logger, 'logger']
+
+ def initialize(env = nil)
+ @env = Env.from(env) if env
+ @on_complete_callbacks = []
+ end
+
+ attr_reader :env
+
+ def_delegators :env, :to_hash
+
+ def status
+ finished? ? env.status : nil
+ end
+
+ def headers
+ finished? ? env.response_headers : {}
+ end
+ def_delegator :headers, :[]
+
+ def body
+ finished? ? env.body : nil
+ end
+
+ def finished?
+ !!env
+ end
+
+ def on_complete
+ if not finished?
+ @on_complete_callbacks << Proc.new
+ else
+ yield(env)
+ end
+ return self
+ end
+
+ def finish(env)
+ raise "response already finished" if finished?
+ @on_complete_callbacks.each { |callback| callback.call(env) }
+ @env = Env.from(env)
+ return self
+ end
+
+ def success?
+ finished? && env.success?
+ end
+
+ # because @on_complete_callbacks cannot be marshalled
+ def marshal_dump
+ !finished? ? nil : {
+ :status => @env.status, :body => @env.body,
+ :response_headers => @env.response_headers
+ }
+ end
+
+ def marshal_load(env)
+ @env = Env.from(env)
+ end
+
+ # Expand the env with more properties, without overriding existing ones.
+ # Useful for applying request params after restoring a marshalled Response.
+ def apply_request(request_env)
+ raise "response didn't finish yet" unless finished?
+ @env = Env.from(request_env).update(@env)
+ return self
+ end
+ end
+end
--- /dev/null
+require 'forwardable'
+
+module Faraday
+ class Response::Logger < Response::Middleware
+ extend Forwardable
+
+ def initialize(app, logger = nil)
+ super(app)
+ @logger = logger || begin
+ require 'logger'
+ ::Logger.new(STDOUT)
+ end
+ end
+
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
+
+ def call(env)
+ info "#{env.method} #{env.url.to_s}"
+ debug('request') { dump_headers env.request_headers }
+ super
+ end
+
+ def on_complete(env)
+ info('Status') { env.status.to_s }
+ debug('response') { dump_headers env.response_headers }
+ end
+
+ private
+
+ def dump_headers(headers)
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Response::RaiseError < Response::Middleware
+ ClientErrorStatuses = 400...600
+
+ def on_complete(env)
+ case env[:status]
+ when 404
+ raise Faraday::Error::ResourceNotFound, response_values(env)
+ when 407
+ # mimic the behavior that we get with proxy requests with HTTPS
+ raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ when ClientErrorStatuses
+ raise Faraday::Error::ClientError, response_values(env)
+ end
+ end
+
+ def response_values(env)
+ {:status => env.status, :headers => env.response_headers, :body => env.body}
+ end
+ end
+end
--- /dev/null
+begin
+ require 'composite_io'
+ require 'parts'
+ require 'stringio'
+rescue LoadError
+ $stderr.puts "Install the multipart-post gem."
+ raise
+end
+
+module Faraday
+ # Similar but not compatible with ::CompositeReadIO provided by multipart-post.
+ class CompositeReadIO
+ def initialize(*parts)
+ @parts = parts.flatten
+ @ios = @parts.map { |part| part.to_io }
+ @index = 0
+ end
+
+ def length
+ @parts.inject(0) { |sum, part| sum + part.length }
+ end
+
+ def rewind
+ @ios.each { |io| io.rewind }
+ @index = 0
+ end
+
+ # Read from IOs in order until `length` bytes have been received.
+ def read(length = nil, outbuf = nil)
+ got_result = false
+ outbuf = outbuf ? outbuf.replace("") : ""
+
+ while io = current_io
+ if result = io.read(length)
+ got_result ||= !result.nil?
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
+ outbuf << result
+ length -= result.length if length
+ break if length == 0
+ end
+ advance_io
+ end
+ (!got_result && length) ? nil : outbuf
+ end
+
+ def close
+ @ios.each { |io| io.close }
+ end
+
+ def ensure_open_and_readable
+ # Rubinius compatibility
+ end
+
+ private
+
+ def current_io
+ @ios[@index]
+ end
+
+ def advance_io
+ @index += 1
+ end
+ end
+
+ UploadIO = ::UploadIO
+ Parts = ::Parts
+end
--- /dev/null
+require 'thread'
+Faraday.require_libs 'parameters'
+
+module Faraday
+ module Utils
+ extend self
+
+ # Adapted from Rack::Utils::HeaderHash
+ class Headers < ::Hash
+ def self.from(value)
+ new(value)
+ end
+
+ def initialize(hash = nil)
+ super()
+ @names = {}
+ self.update(hash || {})
+ end
+
+ # need to synchronize concurrent writes to the shared KeyMap
+ keymap_mutex = Mutex.new
+
+ # symbol -> string mapper + cache
+ KeyMap = Hash.new do |map, key|
+ value = if key.respond_to?(:to_str)
+ key
+ else
+ key.to_s.split('_'). # :user_agent => %w(user agent)
+ each { |w| w.capitalize! }. # => %w(User Agent)
+ join('-') # => "User-Agent"
+ end
+ keymap_mutex.synchronize { map[key] = value }
+ end
+ KeyMap[:etag] = "ETag"
+
+ def [](k)
+ k = KeyMap[k]
+ super(k) || super(@names[k.downcase])
+ end
+
+ def []=(k, v)
+ k = KeyMap[k]
+ k = (@names[k.downcase] ||= k)
+ # join multiple values with a comma
+ v = v.to_ary.join(', ') if v.respond_to? :to_ary
+ super(k, v)
+ end
+
+ def fetch(k, *args, &block)
+ k = KeyMap[k]
+ key = @names.fetch(k.downcase, k)
+ super(key, *args, &block)
+ end
+
+ def delete(k)
+ k = KeyMap[k]
+ if k = @names[k.downcase]
+ @names.delete k.downcase
+ super(k)
+ end
+ end
+
+ def include?(k)
+ @names.include? k.downcase
+ end
+
+ alias_method :has_key?, :include?
+ alias_method :member?, :include?
+ alias_method :key?, :include?
+
+ def merge!(other)
+ other.each { |k, v| self[k] = v }
+ self
+ end
+ alias_method :update, :merge!
+
+ def merge(other)
+ hash = dup
+ hash.merge! other
+ end
+
+ def replace(other)
+ clear
+ self.update other
+ self
+ end
+
+ def to_hash() ::Hash.new.update(self) end
+
+ def parse(header_string)
+ return unless header_string && !header_string.empty?
+ header_string.split(/\r\n/).
+ tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
+ map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
+ each { |key, value|
+ # join multiple values with a comma
+ if self[key]
+ self[key] << ', ' << value
+ else
+ self[key] = value
+ end
+ }
+ end
+ end
+
+ # hash with stringified keys
+ class ParamsHash < Hash
+ def [](key)
+ super(convert_key(key))
+ end
+
+ def []=(key, value)
+ super(convert_key(key), value)
+ end
+
+ def delete(key)
+ super(convert_key(key))
+ end
+
+ def include?(key)
+ super(convert_key(key))
+ end
+
+ alias_method :has_key?, :include?
+ alias_method :member?, :include?
+ alias_method :key?, :include?
+
+ def update(params)
+ params.each do |key, value|
+ self[key] = value
+ end
+ self
+ end
+ alias_method :merge!, :update
+
+ def merge(params)
+ dup.update(params)
+ end
+
+ def replace(other)
+ clear
+ update(other)
+ end
+
+ def merge_query(query, encoder = nil)
+ if query && !query.empty?
+ update((encoder || Utils.default_params_encoder).decode(query))
+ end
+ self
+ end
+
+ def to_query(encoder = nil)
+ (encoder || Utils.default_params_encoder).encode(self)
+ end
+
+ private
+
+ def convert_key(key)
+ key.to_s
+ end
+ end
+
+ def build_query(params)
+ FlatParamsEncoder.encode(params)
+ end
+
+ def build_nested_query(params)
+ NestedParamsEncoder.encode(params)
+ end
+
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
+
+ def escape(s)
+ s.to_s.gsub(ESCAPE_RE) {|match|
+ '%' + match.unpack('H2' * match.bytesize).join('%').upcase
+ }.tr(' ', '+')
+ end
+
+ def unescape(s) CGI.unescape s.to_s end
+
+ DEFAULT_SEP = /[&;] */n
+
+ # Adapted from Rack
+ def parse_query(query)
+ FlatParamsEncoder.decode(query)
+ end
+
+ def parse_nested_query(query)
+ NestedParamsEncoder.decode(query)
+ end
+
+ def default_params_encoder
+ @default_params_encoder ||= NestedParamsEncoder
+ end
+
+ class << self
+ attr_writer :default_params_encoder
+ end
+
+ # Stolen from Rack
+ def normalize_params(params, name, v = nil)
+ name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
+ k = $1 || ''
+ after = $' || ''
+
+ return if k.empty?
+
+ if after == ""
+ if params[k]
+ params[k] = Array[params[k]] unless params[k].kind_of?(Array)
+ params[k] << v
+ else
+ params[k] = v
+ end
+ elsif after == "[]"
+ params[k] ||= []
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
+ params[k] << v
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
+ child_key = $1
+ params[k] ||= []
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
+ if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
+ normalize_params(params[k].last, child_key, v)
+ else
+ params[k] << normalize_params({}, child_key, v)
+ end
+ else
+ params[k] ||= {}
+ raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
+ params[k] = normalize_params(params[k], after, v)
+ end
+
+ return params
+ end
+
+ # Normalize URI() behavior across Ruby versions
+ #
+ # url - A String or URI.
+ #
+ # Returns a parsed URI.
+ def URI(url)
+ if url.respond_to?(:host)
+ url
+ elsif url.respond_to?(:to_str)
+ default_uri_parser.call(url)
+ else
+ raise ArgumentError, "bad argument (expected URI object or URI string)"
+ end
+ end
+
+ def default_uri_parser
+ @default_uri_parser ||= begin
+ require 'uri'
+ Kernel.method(:URI)
+ end
+ end
+
+ def default_uri_parser=(parser)
+ @default_uri_parser = if parser.respond_to?(:call) || parser.nil?
+ parser
+ else
+ parser.method(:parse)
+ end
+ end
+
+ # Receives a String or URI and returns just the path with the query string sorted.
+ def normalize_path(url)
+ url = URI(url)
+ (url.path.start_with?('/') ? url.path : '/' + url.path) +
+ (url.query ? "?#{sort_query_params(url.query)}" : "")
+ end
+
+ # Recursive hash update
+ def deep_merge!(target, hash)
+ hash.each do |key, value|
+ if Hash === value and Hash === target[key]
+ target[key] = deep_merge(target[key], value)
+ else
+ target[key] = value
+ end
+ end
+ target
+ end
+
+ # Recursive hash merge
+ def deep_merge(source, hash)
+ deep_merge!(source.dup, hash)
+ end
+
+ protected
+
+ def sort_query_params(query)
+ query.split('&').sort.join('&')
+ end
+ end
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2013 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+module MultipartPost
+ VERSION = "2.0.0"
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2013 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+require 'parts'
+ module Multipartable
+ DEFAULT_BOUNDARY = "-----------RubyMultipartPost"
+ def initialize(path, params, headers={}, boundary = DEFAULT_BOUNDARY)
+ headers = headers.clone # don't want to modify the original variable
+ parts_headers = headers.delete(:parts) || {}
+ super(path, headers)
+ parts = params.map do |k,v|
+ case v
+ when Array
+ v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
+ else
+ Parts::Part.new(boundary, k, v, parts_headers[k])
+ end
+ end.flatten
+ parts << Parts::EpiloguePart.new(boundary)
+ ios = parts.map {|p| p.to_io }
+ self.set_content_type(headers["Content-Type"] || "multipart/form-data",
+ { "boundary" => boundary })
+ self.content_length = parts.inject(0) {|sum,i| sum + i.length }
+ self.body_stream = CompositeReadIO.new(*ios)
+ end
+ end
--- /dev/null
+#--
+# Copyright (c) 2007-2012 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+require 'net/http'
+require 'stringio'
+require 'cgi'
+require 'composite_io'
+require 'multipartable'
+require 'parts'
+
+module Net #:nodoc:
+ class HTTP #:nodoc:
+ class Put
+ class Multipart < Put
+ include Multipartable
+ end
+ end
+ class Post #:nodoc:
+ class Multipart < Post
+ include Multipartable
+ end
+ end
+ end
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2013 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+module Parts
+ module Part #:nodoc:
+ def self.new(boundary, name, value, headers = {})
+ headers ||= {} # avoid nil values
+ if file?(value)
+ FilePart.new(boundary, name, value, headers)
+ else
+ ParamPart.new(boundary, name, value, headers)
+ end
+ end
+
+ def self.file?(value)
+ value.respond_to?(:content_type) && value.respond_to?(:original_filename)
+ end
+
+ def length
+ @part.length
+ end
+
+ def to_io
+ @io
+ end
+ end
+
+ class ParamPart
+ include Part
+ def initialize(boundary, name, value, headers = {})
+ @part = build_part(boundary, name, value, headers)
+ @io = StringIO.new(@part)
+ end
+
+ def length
+ @part.bytesize
+ end
+
+ def build_part(boundary, name, value, headers = {})
+ part = ''
+ part << "--#{boundary}\r\n"
+ part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
+ part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
+ part << "\r\n"
+ part << "#{value}\r\n"
+ end
+ end
+
+ # Represents a part to be filled from file IO.
+ class FilePart
+ include Part
+ attr_reader :length
+ def initialize(boundary, name, io, headers = {})
+ file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
+ @head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
+ io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
+ @foot = "\r\n"
+ @length = @head.bytesize + file_length + @foot.length
+ @io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
+ end
+
+ def build_head(boundary, name, filename, type, content_len, opts = {}, headers = {})
+ trans_encoding = opts["Content-Transfer-Encoding"] || "binary"
+ content_disposition = opts["Content-Disposition"] || "form-data"
+
+ part = ''
+ part << "--#{boundary}\r\n"
+ part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
+ part << "Content-Length: #{content_len}\r\n"
+ if content_id = opts["Content-ID"]
+ part << "Content-ID: #{content_id}\r\n"
+ end
+
+ if headers["Content-Type"] != nil
+ part << "Content-Type: " + headers["Content-Type"] + "\r\n"
+ else
+ part << "Content-Type: #{type}\r\n"
+ end
+
+ part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
+ part << "\r\n"
+ end
+ end
+
+ # Represents the epilogue or closing boundary.
+ class EpiloguePart
+ include Part
+ def initialize(boundary)
+ @part = "--#{boundary}--\r\n\r\n"
+ @io = StringIO.new(@part)
+ end
+ end
+end
--- /dev/null
+# Add the parent dir to the load path. This is for when
+# Aviator is not installed as a gem
+lib_path = File.dirname(__FILE__)
+$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include? lib_path
+
+require 'aviator/core'
+require "aviator/openstack/provider"
--- /dev/null
+require 'yaml'
+require 'json'
+require 'faraday'
+require 'pathname'
+
+require "aviator/version"
+require "aviator/core/utils/string"
+require "aviator/core/utils/compatibility"
+require "aviator/core/utils/hashish"
+require "aviator/core/request"
+require "aviator/core/request_builder"
+require "aviator/core/response"
+require "aviator/core/service"
+require "aviator/core/session"
+require "aviator/core/logger"
--- /dev/null
+require "terminal-table"
+require "aviator/core/cli/describer"
--- /dev/null
+module Aviator
+
+ class Describer
+
+ class InvalidProviderNameError < StandardError
+ def initialize(name)
+ super "Provider '#{ name }' does not exist."
+ end
+ end
+
+
+ def self.describe_aviator
+ str = "Available providers:\n"
+
+ provider_names.each do |provider_name|
+ str << " #{ provider_name }\n"
+ end
+
+ str
+ end
+
+
+ def self.describe_provider(provider_name)
+ str = "Available services for #{ provider_name }:\n"
+
+ service_names(provider_name).each do |service_name|
+ str << " #{ service_name }\n"
+ end
+
+ str
+ end
+
+
+ def self.describe_request(provider_name, service_name, api_version, endpoint_type, request_name)
+ service = Aviator::Service.new :provider => provider_name, :service => service_name
+ request_class = "Aviator::#{ provider_name.camelize }::#{ service_name.camelize }::Requests::"\
+ "#{ api_version.camelize }::#{ endpoint_type.camelize }::#{ request_name.camelize }".constantize
+
+ display = "Request: #{ request_name }\n"
+
+
+ # Build the parameters
+ params = request_class.optional_params.map{|p| [p, false]} +
+ request_class.required_params.map{|p| [p, true]}
+
+ aliases = request_class.param_aliases
+
+ if params.length > 0
+ display << "\n"
+
+ headings = ['NAME', 'REQUIRED?']
+
+ headings << 'ALIAS' if aliases.length > 0
+
+ rows = []
+ params.sort{|a,b| a[0].to_s <=> b[0].to_s }.each do |param|
+ row = [ param[0], param[1] ? 'Y' : 'N' ]
+
+ if aliases.length > 0
+ row << (aliases.find{|a,p| p == param[0] } || [''])[0]
+ end
+
+ rows << row
+ end
+
+ widths = [
+ rows.map{|row| row[0].to_s.length }.max,
+ rows.map{|row| row[1].to_s.length }.max
+ ]
+
+ widths << rows.map{|row| row[2].to_s.length }.max if aliases.length > 0
+
+ table = Terminal::Table.new(:headings => headings, :rows => rows)
+
+ table.align_column(1, :center)
+
+ display << "Parameters:\n"
+ display << " " + table.to_s.split("\n").join("\n ")
+ display << "\n"
+ end
+
+
+ # Build the sample code
+ display << "\nSample Code:\n"
+
+ display << " session.request(:#{ service_name }_service, :#{ request_name })"
+
+ if params && params.length > 0
+ display << " do |params|\n"
+ params.each do |pair|
+ display << " params.#{ (aliases.find{|a,p| p == pair[0] } || pair)[0] } = value\n"
+ end
+ display << " end"
+ end
+
+ display << "\n"
+
+
+ # Build the links
+ if request_class.links && request_class.links.length > 0
+ display << "\nLinks:\n"
+
+ request_class.links.each do |link|
+ display << " #{ link[:rel] }:\n"
+ display << " #{ link[:href] }\n"
+ end
+ end
+
+ display
+ end
+
+
+ def self.describe_service(provider_name, service_name)
+ requests = request_classes(provider_name, service_name)
+
+ if requests.empty?
+ str = "No requests found for #{ provider_name } #{ service_name }_service."
+ else
+ str = "Available requests for #{ provider_name } #{ service_name }_service:\n"
+
+ requests.each do |klass|
+ str << " #{ klass.api_version } #{ klass.endpoint_type } #{ klass.name.split('::').last.underscore }\n"
+ end
+
+ str
+ end
+ end
+
+
+ class <<self
+ private
+
+ def provider_names
+ Pathname.new(__FILE__) \
+ .join('..', '..', '..') \
+ .children \
+ .select{|c| c.directory? && c.basename.to_s != 'core' } \
+ .map{|c| c.basename.to_s }
+ end
+
+
+ def request_classes(provider_name, service_name)
+ service = Aviator::Service.new(:provider => provider_name, :service => service_name)
+ service.request_classes
+ end
+
+
+ def service_names(name)
+ provider = Pathname.new(__FILE__).join('..', '..', '..', name)
+
+ raise InvalidProviderNameError.new(name) unless provider.exist?
+
+ provider.children \
+ .select{|c| c.directory? } \
+ .map{|c| c.basename.to_s }
+ end
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class Logger < Faraday::Response::Middleware
+ extend Forwardable
+
+ def initialize(app, logger=nil)
+ super(app)
+ @logger = logger || begin
+ require 'logger'
+ ::Logger.new(self.class::LOG_FILE_PATH)
+ end
+ end
+
+
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
+
+
+ def call(env)
+ info(env[:method].to_s.upcase) { env[:url].to_s }
+ debug('REQ_HEAD') { dump_headers env[:request_headers] }
+ debug('REQ_BODY') { dump_body env[:body] }
+ super
+ end
+
+
+ def on_complete(env)
+ info('STATUS') { env[:status].to_s }
+ debug('RES_HEAD') { dump_headers env[:response_headers] }
+ debug('RES_BODY') { dump_body env[:body] }
+ end
+
+
+ def self.configure(log_file_path)
+ # Return a subclass with its logfile path set. This
+ # must be done so that different sessions can log to
+ # different paths.
+ Class.new(self) { const_set('LOG_FILE_PATH', log_file_path) }
+ end
+
+
+ private
+
+ def dump_body(body)
+ return if body.nil?
+
+ # :TODO => Make this configurable
+ body.gsub(/["']password["']:["']\w*["']/, '"password":[FILTERED_VALUE]')
+ end
+
+ def dump_headers(headers)
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("; ")
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class Request
+
+ class ApiVersionNotDefinedError < StandardError
+ def initialize
+ super "api_version is not defined."
+ end
+ end
+
+ class EndpointTypeNotDefinedError < StandardError
+ def initialize
+ super "endpoint_type is not defined."
+ end
+ end
+
+ class PathNotDefinedError < StandardError
+ def initialize
+ super "path is not defined."
+ end
+ end
+
+
+ def initialize(session_data=nil)
+ @session_data = session_data
+
+ params = self.class.params_class.new if self.class.params_class
+
+ if params
+ yield(params) if block_given?
+ validate_params(params)
+ end
+
+ @params = params
+ end
+
+
+ def anonymous?
+ self.class.anonymous?
+ end
+
+
+ def body?
+ self.class.body?
+ end
+
+
+ def headers?
+ self.class.headers?
+ end
+
+
+ def links
+ self.class.links
+ end
+
+
+ def optional_params
+ self.class.optional_params
+ end
+
+
+ def params
+ @params.dup
+ end
+
+
+ def required_params
+ self.class.required_params
+ end
+
+
+ def session_data
+ @session_data
+ end
+
+
+ def session_data?
+ !session_data.nil?
+ end
+
+
+ def querystring?
+ self.class.querystring?
+ end
+
+
+ def url?
+ self.class.url?
+ end
+
+
+ private
+
+
+ def validate_params(params)
+ required_params = self.class.required_params
+
+ required_params.each do |name|
+ raise ArgumentError.new("Missing parameter #{ name }.") if params.send(name).nil?
+ end
+ end
+
+
+ # NOTE that, because we are defining the following as class methods, when they
+ # are called, all 'instance' variables are actually defined in the descendant class,
+ # not in the instance/object. This is by design since we want to keep these attributes
+ # within the class and because they don't change between instances anyway.
+ class << self
+
+ def anonymous?
+ respond_to?(:anonymous) && anonymous == true
+ end
+
+
+ def body?
+ instance_methods.include? :body
+ end
+
+
+ def headers?
+ instance_methods.include? :headers
+ end
+
+
+ def links
+ @links ||= []
+ end
+
+
+ def param_aliases
+ @param_aliases ||= {}
+ end
+
+
+ def params_class
+ all_params = required_params + optional_params
+
+ if all_params.length > 0 && @params_class.nil?
+ @params_class = build_params_class(all_params, self.param_aliases)
+ end
+
+ @params_class
+ end
+
+
+ def optional_params
+ @optional_params ||= []
+ end
+
+
+ def querystring?
+ instance_methods.include? :querystring
+ end
+
+
+ def required_params
+ @required_params ||= []
+ end
+
+
+ def url?
+ instance_methods.include? :url
+ end
+
+
+ private
+
+
+ def build_params_class(all_params, param_aliases)
+ Struct.new(*all_params) do
+ alias :param_getter :[]
+ alias :param_setter :[]=
+
+ define_method :[] do |key|
+ key = param_aliases[key.to_sym] if param_aliases.keys.include? key.to_sym
+ param_getter(key)
+ end
+
+ define_method :[]= do |key, value|
+ key = param_aliases[key.to_sym] if param_aliases.keys.include? key.to_sym
+ param_setter(key, value)
+ end
+
+ param_aliases.each do |param_alias, param_name|
+ define_method param_alias do
+ param_getter(param_name)
+ end
+
+ define_method "#{ param_alias }=" do |value|
+ param_setter(param_name, value)
+ end
+ end
+ end
+ end
+
+
+ def link(rel, href)
+ links << { :rel => rel, :href => href }
+ end
+
+
+ def meta(attr_name, attr_value)
+ eigenclass = class << self; self; end
+ eigenclass.send(:define_method, attr_name) do
+ attr_value
+ end
+
+ define_method(attr_name) do
+ self.class.send(attr_name)
+ end
+ end
+
+
+ def param(param_name, opts={})
+ opts = Hashish.new(opts)
+ list = (opts[:required] == false ? optional_params : required_params)
+ list << param_name unless optional_params.include?(param_name)
+
+ if opts[:alias]
+ self.param_aliases[opts[:alias]] = param_name
+ end
+ end
+
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ class BaseRequestNotFoundError < StandardError
+ attr_reader :base_request_hierarchy
+
+ def initialize(base_hierarchy)
+ @base_request_hierarchy = base_hierarchy
+ super("#{ base_request_hierarchy } could not be found!")
+ end
+ end
+
+
+ class RequestAlreadyDefinedError < StandardError
+ attr_reader :namespace,
+ :request_name
+
+ def initialize(namespace, request_name)
+ @namespace = namespace
+ @request_name = request_name
+ super("#{ namespace }::#{ request_name } is already defined")
+ end
+ end
+
+
+ class RequestBuilder
+
+ class << self
+
+ def define_request(root_namespace, request_name, options, &block)
+ base_klass = get_request_class(root_namespace, options[:inherit])
+
+ klass = Class.new(base_klass, &block)
+
+ namespace_arr = [
+ klass.provider,
+ klass.service,
+ 'Requests',
+ klass.api_version,
+ klass.endpoint_type
+ ]
+
+ namespace = namespace_arr.inject(root_namespace) do |namespace, sym|
+ const_name = sym.to_s.camelize
+ namespace.const_set(const_name, Module.new) unless namespace.const_defined?(const_name, false)
+ namespace.const_get(const_name, false)
+ end
+
+ klassname = request_name.to_s.camelize
+
+ if namespace.const_defined?(klassname, false)
+ raise RequestAlreadyDefinedError.new(namespace, klassname)
+ end
+
+ namespace.const_set(klassname, klass)
+ end
+
+
+ def get_request_class(root_namespace, request_class_arr)
+ provider_specific = request_class_arr != [:request]
+
+ if provider_specific
+ full_request_class_arr = request_class_arr.dup
+ full_request_class_arr.insert(2, :requests) if provider_specific
+ else
+ full_request_class_arr = request_class_arr
+ end
+
+ full_request_class_arr.inject(root_namespace) do |namespace, sym|
+ namespace.const_get(sym.to_s.camelize, false)
+ end
+ rescue NameError => e
+ if Aviator.const_defined?(full_request_class_arr[0].to_s.camelize)
+ provider = "Aviator::#{ full_request_class_arr[0] }::Provider".constantize
+ arr = ['..'] + full_request_class_arr
+ arr[-1,1] = arr.last.to_s + '.rb'
+ path = Pathname.new(provider.root_dir).join(*arr.map{|i| i.to_s }).expand_path
+ end
+
+ if provider && path.exist?
+ require path
+ full_request_class_arr.inject(root_namespace) do |namespace, sym|
+ namespace.const_get(sym.to_s.camelize, false)
+ end
+ else
+ raise BaseRequestNotFoundError.new(request_class_arr)
+ end
+ end
+
+ end
+
+ end
+
+
+ class << self
+
+ def define_request(request_name, options={ :inherit => [:request] }, &block)
+ RequestBuilder.define_request self, request_name, options, &block
+ end
+
+ end # class << self
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ class Response
+ extend Forwardable
+
+ def_delegators :@response, :status
+
+ attr_reader :request
+
+ def initialize(response, request)
+ @response = response
+ @request = request
+ end
+
+
+ def body
+ @body ||= if raw_body.length > 0
+ if Aviator::Compatibility::RUBY_1_8_MODE
+ clean_body = raw_body.gsub(/\\ /, ' ')
+ else
+ clean_body = raw_body
+ end
+
+ Hashish.new(JSON.parse(clean_body))
+ else
+ Hashish.new({})
+ end
+ end
+
+
+ def headers
+ @headers ||= Hashish.new(@response.headers)
+ end
+
+
+ def to_hash
+ Hashish.new({
+ :status => status,
+ :headers => headers,
+ :body => body
+ })
+ end
+
+ private
+
+ def raw_body
+ @raw_body ||= @response.body
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ #
+ # Manages a service
+ #
+ class Service
+
+ class AccessDetailsNotDefinedError < StandardError
+ def initialize
+ super ":access_details is not defined."
+ end
+ end
+
+ class ProviderNotDefinedError < StandardError
+ def initialize
+ super ":provider is not defined."
+ end
+ end
+
+ class ServiceNameNotDefinedError < StandardError
+ def initialize
+ super ":service is not defined."
+ end
+ end
+
+ class SessionDataNotProvidedError < StandardError
+ def initialize(service_name, request_name)
+ super "\n\nERROR: default_session_data is not initialized and no session data was provided in\n"\
+ "the method call. You have two ways to fix this:\n\n"\
+ " 1) Call Session#authenticate before calling Session##{service_name}_service, or\n\n"\
+ " 2) If you're really sure you don't want to authenticate beforehand,\n"\
+ " construct the method call this way:\n\n"\
+ " service = session.#{service_name}_service\n"\
+ " service.request :#{request_name}, :api_version => :v2, :session_data => sessiondatavar\n\n"\
+ " Replace :v2 with whatever available version you want to use and make sure sessiondatavar\n"\
+ " is a hash that contains, at least, the :base_url key. Other keys, such as :service_token may\n"\
+ " be needed depending on what the request class you are calling requires.\n\n"
+ end
+ end
+
+ class UnknownRequestError < StandardError
+ def initialize(request_name, options)
+ super "Unknown request #{ request_name } #{ options }."
+ end
+ end
+
+
+ class MissingServiceEndpointError < StandardError
+ def initialize(service_name, request_name)
+ request_name = request_name.to_s.split('::').last.underscore
+ super "The session's service catalog does not have an entry for the #{ service_name } "\
+ "service. Therefore, I don't know to which base URL the request should be sent. "\
+ "This may be because you are using a default or unscoped token. If this is not your "\
+ "intention, please authenticate with a scoped token. If using a default token is your "\
+ "intention, make sure to provide a base url when you call the request. For :example => \n\n"\
+ "session.#{ service_name }_service.request :#{ request_name }, :base_url => 'http://myenv.com:9999/v2.0' do |params|\n"\
+ " params[:example1] = 'example1'\n"\
+ " params[:example2] = 'example2'\n"\
+ "end\n\n"
+ end
+ end
+
+ attr_accessor :default_session_data
+
+ attr_reader :service,
+ :provider
+
+
+ def initialize(opts={})
+ @provider = opts[:provider] || (raise ProviderNotDefinedError.new)
+ @service = opts[:service] || (raise ServiceNameNotDefinedError.new)
+ @log_file = opts[:log_file]
+ @default_options = opts[:default_options] || {}
+
+ @default_session_data = opts[:default_session_data]
+
+ load_requests
+ end
+
+ #
+ # No longer recommended for public use. Use Aviator::Session#request instead
+ #
+ def request(request_name, options={}, ¶ms)
+ if options[:api_version].nil? && @default_options[:api_version]
+ options[:api_version] = @default_options[:api_version]
+ end
+
+ session_data = options[:session_data] || default_session_data
+
+ raise SessionDataNotProvidedError.new(@service, request_name) unless session_data
+
+ [:base_url].each do |k|
+ session_data[k] = options[k] if options[k]
+ end
+
+ request_class = provider_module.find_request(service, request_name, session_data, options)
+
+ raise UnknownRequestError.new(request_name, options) unless request_class
+
+ # Always use :params over ¶ms if provided
+ if options[:params]
+ params = lambda do |params|
+ options[:params].each do |key, value|
+ begin
+ params[key] = value
+ rescue NameError => e
+ raise NameError.new("Unknown param name '#{key}'")
+ end
+ end
+ end
+ end
+
+ request = request_class.new(session_data, ¶ms)
+
+ response = http_connection.send(request.http_method) do |r|
+ r.url request.url
+ r.headers.merge!(request.headers) if request.headers?
+ r.query = request.querystring if request.querystring?
+ r.body = JSON.generate(request.body) if request.body?
+ end
+
+ Aviator::Response.send(:new, response, request)
+ end
+
+
+ def request_classes
+ @request_classes
+ end
+
+
+ private
+
+
+ def http_connection
+ @http_connection ||= Faraday.new do |conn|
+ conn.use Logger.configure(log_file) if log_file
+ conn.adapter Faraday.default_adapter
+
+ conn.headers['Content-Type'] = 'application/json'
+ end
+ end
+
+
+ def load_requests
+ request_file_paths = provider_module.request_file_paths(service)
+ request_file_paths.each{ |path| require path }
+
+ constant_parts = request_file_paths \
+ .map{|rf| rf.to_s.match(/#{provider}\/#{service}\/([\w\/]+)\.rb$/) } \
+ .map{|rf| rf[1].split('/').map{|c| c.camelize }.join('::') }
+
+ @request_classes = constant_parts.map do |cp|
+ "Aviator::#{provider.camelize}::#{service.camelize}::#{cp}".constantize
+ end
+ end
+
+
+ def log_file
+ @log_file
+ end
+
+
+ def provider_module
+ @provider_module ||= "Aviator::#{provider.camelize}::Provider".constantize
+ end
+
+ end
+
+end
--- /dev/null
+#
+# Author:: Mark Maglana (mmaglana@gmail.com)
+# Copyright:: Copyright (c) 2014 Mark Maglana
+# License:: Distributed under the MIT license
+# Homepage:: http://aviator.github.io/www/
+#
+module Aviator
+
+ #
+ # Manages a provider (e.g. OpenStack) session and serves as the entry point
+ # for a consumer class/object. See Session::new for notes on usage.
+ #
+ class Session
+
+ class AuthenticationError < StandardError
+ def initialize(last_auth_body)
+ super("Authentication failed. The server returned #{ last_auth_body }")
+ end
+ end
+
+
+ class EnvironmentNotDefinedError < ArgumentError
+ def initialize(path, env)
+ super("The environment '#{ env }' is not defined in #{ path }.")
+ end
+ end
+
+ class InitializationError < StandardError
+ def initialize
+ super("The session could not find :session_dump, :config_file, and " \
+ ":config in the constructor arguments provided")
+ end
+ end
+
+ class InvalidConfigFilePathError < ArgumentError
+ def initialize(path)
+ super("The config file at #{ path } does not exist!")
+ end
+ end
+
+
+ class NotAuthenticatedError < StandardError
+ def initialize
+ super("Session is not authenticated. Please authenticate before proceeding.")
+ end
+ end
+
+
+ class ValidatorNotDefinedError < StandardError
+ def initialize
+ super("The validator request name is not defined for this session object.")
+ end
+ end
+
+ #
+ # Create a new Session instance.
+ #
+ # <b>Initialize with a config file</b>
+ #
+ # Aviator::Session.new(:config_file => 'path/to/aviator.yml', :environment => :production)
+ #
+ # In the above example, the config file must have the following form:
+ #
+ # production:
+ # provider: openstack
+ # auth_service:
+ # name: identity
+ # host_uri: 'http://my.openstackenv.org:5000'
+ # request: create_token
+ # validator: list_tenants
+ # api_version: v2
+ # auth_credentials:
+ # username: myusername
+ # password: mypassword
+ # tenant_name: myproject
+ #
+ # <b>SIDENOTE:</b> For more information about the <tt>validator</tt> member, see Session#validate.
+ #
+ # Once the session has been instantiated, you may authenticate against the
+ # provider as follows:
+ #
+ # session.authenticate
+ #
+ # The members you put under <tt>auth_credentials</tt> will depend on the request
+ # class you declare under <tt>auth_service:request</tt> and what parameters it
+ # accepts. To know more about a request class and its parameters, you can use
+ # the CLI tool <tt>aviator describe</tt> or view the request definition file directly.
+ #
+ # If writing the <tt>auth_credentials</tt> in the config file is not acceptable,
+ # you may omit it and just supply the credentials at runtime. For example:
+ #
+ # session.authenticate do |params|
+ # params.username = ARGV[0]
+ # params.password = ARGV[1]
+ # params.tenant_name = ARGV[2]
+ # end
+ #
+ # See Session#authenticate for more info.
+ #
+ # Note that while the example config file above only has one environment (production),
+ # you can declare an arbitrary number of environments in your config file. Shifting
+ # between environments is as simple as changing the <tt>:environment</tt> to refer to that.
+ #
+ #
+ # <b>Initialize with an in-memory hash</b>
+ #
+ # You can create an in-memory hash with a structure similar to the config file but without
+ # the environment name. For example:
+ #
+ # configuration = {
+ # :provider => 'openstack',
+ # :auth_service => {
+ # :name => 'identity',
+ # :host_uri => 'http://devstack:5000/v2.0',
+ # :request => 'create_token',
+ # :validator => 'list_tenants'
+ # }
+ # }
+ #
+ # Supply this to the initializer using the <tt>:config</tt> option. For example:
+ #
+ # Aviator::Session.new(:config => configuration)
+ #
+ #
+ # <b>Initialize with a session dump</b>
+ #
+ # You can create a new Session instance using a dump from another instance. For example:
+ #
+ # session_dump = session1.dump
+ # session2 = Aviator::Session.new(:session_dump => session_dump)
+ #
+ # However, Session.load is cleaner and recommended over this method.
+ #
+ #
+ # <b>Optionally supply a log file</b>
+ #
+ # In all forms above, you may optionally add a <tt>:log_file</tt> option to make
+ # Aviator write all HTTP calls to the given path. For example:
+ #
+ # Aviator::Session.new(:config_file => 'path/to/aviator.yml', :environment => :production, :log_file => 'path/to/log')
+ #
+ def initialize(opts={})
+ if opts.has_key? :session_dump
+ initialize_with_dump(opts[:session_dump])
+ elsif opts.has_key? :config_file
+ initialize_with_config(opts[:config_file], opts[:environment])
+ elsif opts.has_key? :config
+ initialize_with_hash(opts[:config])
+ else
+ raise InitializationError.new
+ end
+
+ @log_file = opts[:log_file]
+ end
+
+ #
+ # Authenticates against the backend provider using the auth_service request class
+ # declared in the session's configuration. Please see Session.new for more information
+ # on declaring the request class to use for authentication.
+ #
+ # <b>Request params block</b>
+ #
+ # If the auth_service request class accepts parameters, you may supply that
+ # as a block and it will be directly passed to the request. For example:
+ #
+ # session = Aviator::Session.new(:config => config)
+ # session.authenticate do |params|
+ # params.username = username
+ # params.password = password
+ # params.tenant_name = project
+ # end
+ #
+ # If your configuration happens to have an <tt>auth_credentials</tt> in it, those
+ # will be overridden by this block.
+ #
+ # <b>Treat parameters as a hash</b>
+ #
+ # You can also treat the params struct like a hash with the attribute
+ # names as the keys. For example, we can rewrite the above as:
+ #
+ # session = Aviator::Session.new(:config => config)
+ # session.authenticate do |params|
+ # params[:username] = username
+ # params[:password] = password
+ # params[:tenant_name] = project
+ # end
+ #
+ # Keys can be symbols or strings.
+ #
+ # <b>Use a hash argument instead of a block</b>
+ #
+ # You may also provide request params as an argument instead of a block. This is
+ # especially useful if you want to mock Aviator as it's easier to specify ordinary
+ # argument expectations over blocks. Further rewriting the example above,
+ # we end up with:
+ #
+ # session = Aviator::Session.new(:config => config)
+ # session.authenticate :params => {
+ # :username => username,
+ # :password => password,
+ # :tenant_name => project
+ # }
+ #
+ # If both <tt>:params</tt> and a block are provided, the <tt>:params</tt>
+ # values will be used and the block ignored.
+ #
+ # <b>Success requirements</b>
+ #
+ # Expects an HTTP status 200 or 201 response from the backend. Any other
+ # status is treated as a failure.
+ #
+ def authenticate(opts={}, &block)
+ block ||= lambda do |params|
+ config[:auth_credentials].each do |key, value|
+ begin
+ params[key] = value
+ rescue NameError => e
+ raise NameError.new("Unknown param name '#{key}'")
+ end
+ end
+ end
+
+ response = auth_service.request(config[:auth_service][:request].to_sym, opts, &block)
+
+ if [200, 201].include? response.status
+ @auth_response = Hashish.new({
+ :headers => response.headers,
+ :body => response.body
+ })
+ update_services_session_data
+ else
+ raise AuthenticationError.new(response.body)
+ end
+ self
+ end
+
+ #
+ # Returns true if the session has been authenticated. Note that this relies on
+ # cached response from a previous run of Session#authenticate if one was made.
+ # If you want to check against the backend provider if the session is still valid,
+ # use Session#validate instead.
+ #
+ def authenticated?
+ !auth_response.nil?
+ end
+
+ #
+ # Returns its configuration.
+ #
+ def config
+ @config
+ end
+
+ #
+ # Returns a JSON string of its configuration and auth_data. This string can be streamed
+ # or stored and later re-loaded in another Session instance. For example:
+ #
+ # session = Aviator::Session.new(:config => configuration)
+ # str = session.dump
+ #
+ # # time passes...
+ #
+ # session = Aviator::Session.load(str)
+ #
+ def dump
+ JSON.generate({
+ :config => config,
+ :auth_response => auth_response
+ })
+ end
+
+
+ #
+ # Same as Session::load but re-uses the Session instance this method is
+ # called on instead of creating a new one.
+ #
+ def load(session_dump)
+ initialize_with_dump(session_dump)
+ update_services_session_data
+ self
+ end
+
+
+ def method_missing(name, *args, &block) # :nodoc:
+ service_name_parts = name.to_s.match(/^(\w+)_service$/)
+
+ if service_name_parts
+ get_service_obj(service_name_parts[1])
+ else
+ super name, *args, &block
+ end
+ end
+
+
+ #
+ # Creates a new Session object from a previous session's dump. See Session#dump for
+ # more information.
+ #
+ # If you want the newly deserialized session to log its output, add a <tt>:log_file</tt>
+ # option.
+ #
+ # Aviator::Session.load(session_dump_str, :log_file => 'path/to/aviator.log')
+ #
+ def self.load(session_dump, opts={})
+ opts[:session_dump] = session_dump
+
+ new(opts)
+ end
+
+
+ #
+ # Returns the log file path. May be nil if none was provided during initialization.
+ #
+ def log_file
+ @log_file
+ end
+
+
+ #
+ # Calls the given request of the given service. An example call might look like:
+ #
+ # session.request :compute_service, :create_server do |p|
+ # p.name = "My Server"
+ # p.image_ref = "7cae8c8e-fb01-4a88-bba3-ae0fcb1dbe29"
+ # p.flavor_ref = "fa283da1-59a5-4245-8569-b6eadf69f10b"
+ # end
+ #
+ # Note that you can also treat the block's argument like a hash with the attribute
+ # names as the keys. For example, we can rewrite the above as:
+ #
+ # session.request :compute_service, :create_server do |p|
+ # p[:name] = "My Server"
+ # p[:image_ref] = "7cae8c8e-fb01-4a88-bba3-ae0fcb1dbe29"
+ # p[:flavor_ref] = "fa283da1-59a5-4245-8569-b6eadf69f10b"
+ # end
+ #
+ # Keys can be symbols or strings.
+ #
+ # You may also provide parameters as an argument instead of a block. This is
+ # especially useful when mocking Aviator as it's easier to specify ordinary
+ # argument expectations over blocks. Further rewriting the example above,
+ # we end up with:
+ #
+ # session.request :compute_service, :create_server, :params => {
+ # :name => "My Server",
+ # :image_ref => "7cae8c8e-fb01-4a88-bba3-ae0fcb1dbe29",
+ # :flavor_ref => "fa283da1-59a5-4245-8569-b6eadf69f10b"
+ # }
+ #
+ # If both <tt>:params</tt> and a block are provided, the values in <tt>:params</tt>
+ # will be used and the block ignored.
+ #
+ # <b>Return Value</b>
+ #
+ # The return value will be an instance of Hashish, a lightweight replacement for
+ # activesupport's HashWithIndifferentAccess, with the following structure:
+ #
+ # {
+ # :status => 200,
+ # :headers => {
+ # 'X-Auth-Token' => 'd9186f45ce5446eaa0adc9def1c46f5f',
+ # 'Content-Type' => 'application/json'
+ # },
+ # :body => {
+ # :some_key => :some_value
+ # }
+ # }
+ #
+ # Note that the members in <tt>:headers</tt> and <tt>:body</tt> will vary depending
+ # on the provider and the request that was made.
+ #
+ # ---
+ #
+ # <b>Request Options</b>
+ #
+ # You can further customize how the method behaves by providing one or more
+ # options to the call. For example, assuming you are using the <tt>openstack</tt>
+ # provider, the following will call the <tt>:create_server</tt> request of the
+ # v1 API of <tt>:compute_service</tt>.
+ #
+ # session.request :compute_service, :create_server, :api_version => v1, :params => params
+ #
+ # The available options vary depending on the provider. See the documentation
+ # on the provider's Provider class for more information (e.g. Aviator::Openstack::Provider)
+ #
+ def request(service_name, request_name, opts={}, &block)
+ service = send("#{service_name.to_s}_service")
+ response = service.request(request_name, opts, &block)
+ response.to_hash
+ end
+
+
+ #
+ # Returns true if the session is still valid in the underlying provider. This method calls
+ # the <tt>validator</tt> request class declared under <tt>auth_service</tt> in the
+ # configuration. The validator can be any request class as long as:
+ #
+ # * The request class exists!
+ # * Is not an anonymous request. Otherwise it will always return true.
+ # * Does not require any parameters
+ # * It returns an HTTP status 200 or 203 to indicate auth info validity.
+ # * It returns any other HTTP status to indicate that the auth info is invalid.
+ #
+ # See Session::new for an example on how to specify the request class to use for session validation.
+ #
+ # Note that this method requires the session to be previously authenticated otherwise a
+ # NotAuthenticatedError will be raised. If you just want to check if the session was previously
+ # authenticated, use Session#authenticated? instead.
+ #
+ def validate
+ raise NotAuthenticatedError.new unless authenticated?
+ raise ValidatorNotDefinedError.new unless config[:auth_service][:validator]
+
+ auth_with_bootstrap = auth_response.merge({ :auth_service => config[:auth_service] })
+
+ response = auth_service.request config[:auth_service][:validator].to_sym, :session_data => auth_with_bootstrap
+ response.status == 200 || response.status == 203
+ end
+
+
+ private
+
+
+ def auth_response
+ @auth_response
+ end
+
+
+ def auth_service
+ @auth_service ||= Service.new(
+ :provider => config[:provider],
+ :service => config[:auth_service][:name],
+ :default_session_data => { :auth_service => config[:auth_service] },
+ :log_file => log_file
+ )
+ end
+
+
+ def get_service_obj(service_name)
+ @services ||= {}
+
+ if @services[service_name].nil?
+ default_options = config["#{ service_name }_service"]
+
+ @services[service_name] = Service.new(
+ :provider => config[:provider],
+ :service => service_name,
+ :default_session_data => auth_response,
+ :default_options => default_options,
+ :log_file => log_file
+ )
+ end
+
+ @services[service_name]
+ end
+
+
+ def initialize_with_config(config_path, environment)
+ raise InvalidConfigFilePathError.new(config_path) unless Pathname.new(config_path).file?
+
+ all_config = Hashish.new(YAML.load_file(config_path))
+
+ raise EnvironmentNotDefinedError.new(config_path, environment) unless all_config[environment]
+
+ @config = all_config[environment]
+ end
+
+
+ def initialize_with_dump(session_dump)
+ session_info = Hashish.new(JSON.parse(session_dump))
+ @config = session_info[:config]
+ @auth_response = session_info[:auth_response]
+ end
+
+
+ def initialize_with_hash(hash_obj)
+ @config = Hashish.new(hash_obj)
+ end
+
+
+ def update_services_session_data
+ return unless @services
+
+ @services.each do |name, obj|
+ obj.default_session_data = auth_response
+ end
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ module Compatibility
+ RUBY_1_8_MODE = (not (RUBY_VERSION =~ /1\.8\.\d*/).nil?)
+ end
+
+end
+
+if Aviator::Compatibility::RUBY_1_8_MODE
+
+ class Module
+
+ alias_method :old_const_defined?, :const_defined?
+
+ def const_defined?(sym, ignore=nil)
+ old_const_defined?(sym)
+ end
+
+
+ alias_method :old_const_get, :const_get
+
+ def const_get(sym, ignore=nil)
+ old_const_get(sym)
+ end
+
+ alias_method :old_instance_methods, :instance_methods
+
+ def instance_methods(include_super=true)
+ old_instance_methods(include_super).map(&:to_sym)
+ end
+
+ end
+
+end
--- /dev/null
+require 'json'
+
+# Hash-ish!
+#
+# This class is implemented using composition rather than inheritance so
+# that we have control over what operations it exposes to peers.
+class Hashish
+ include Enumerable
+
+ def initialize(hash={})
+ @hash = hash.dup
+ stringify_keys
+ hashishify_values
+ end
+
+ def ==(other_obj)
+ other_obj.class == self.class &&
+ other_obj.hash == self.hash
+ end
+
+ def [](key)
+ @hash[normalize(key)]
+ end
+
+ def []=(key, value)
+ @hash[normalize(key)] = value
+ end
+
+ def dup
+ Hashish.new(JSON.parse(@hash.to_json))
+ end
+
+ def each(&block)
+ @hash.each(&block)
+ end
+
+ def empty?
+ @hash.empty?
+ end
+
+ def has_key?(name)
+ @hash.has_key? normalize(name)
+ end
+
+ def hash
+ @hash
+ end
+
+ def keys
+ @hash.keys
+ end
+
+ def length
+ @hash.length
+ end
+
+ def merge(other_hash)
+ Hashish.new(@hash.merge(other_hash))
+ end
+
+ def merge!(other_hash)
+ @hash.merge! other_hash
+ self
+ end
+
+ def to_json(obj)
+ @hash.to_json(obj)
+ end
+
+ def to_s
+ str = "{"
+ @hash.each do |key, value|
+ if value.kind_of? String
+ value = "'#{value}'"
+ elsif value.nil?
+ value = "nil"
+ elsif value.kind_of? Array
+ value = "[#{value.join(", ")}]"
+ end
+
+ str += " #{key}: #{value},"
+ end
+
+ str = str[0...-1] + " }"
+ str
+ end
+
+ private
+
+ # Hashishify all the things!
+ def hashishify_values
+ @hash.each do |key, value|
+ if @hash[key].kind_of? Hash
+ @hash[key] = Hashish.new(value)
+ elsif @hash[key].kind_of? Array
+ @hash[key].each_index do |index|
+ element = @hash[key][index]
+ if element.kind_of? Hash
+ @hash[key][index] = Hashish.new(element)
+ end
+ end
+ end
+ end
+ end
+
+
+ def normalize(key)
+ if @hash.has_key? key
+ key
+ elsif key.is_a? Symbol
+ key.to_s
+ else
+ key
+ end
+ end
+
+
+ def stringify_keys
+ keys = @hash.keys
+ keys.each do |key|
+ if key.is_a? Symbol
+ @hash[key.to_s] = @hash.delete(key)
+ end
+ end
+ end
+
+end
--- /dev/null
+class String
+
+ unless instance_methods.include? 'camelize'
+ define_method :camelize do
+ word = self.slice(0,1).capitalize + self.slice(1..-1)
+ word.gsub(/_([a-zA-Z\d])/) { "#{$1.capitalize}" }
+ end
+ end
+
+ unless instance_methods.include? 'constantize'
+ define_method :constantize do
+ self.split("::").inject(Object) do |namespace, sym|
+ namespace.const_get(sym.to_s.camelize, false)
+ end
+ end
+ end
+
+ unless instance_methods.include? 'underscore'
+ define_method :underscore do
+ self.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :base do
+
+ meta :provider, :openstack
+ meta :service, :common
+ meta :api_version, :v0
+ meta :endpoint_type, :public
+
+ def headers
+ {}.tap do |h|
+ if self.anonymous?
+ # do nothing
+
+ elsif session_data.has_key? :service_token
+ # service_token is the token that one would bootstrap OpenStack
+ # with during the installation process. This token can be used
+ # directly and does not require authentication beforehand.
+ h['X-Auth-Token'] = session_data[:service_token]
+
+ elsif keystone_v2_style_session_data?
+ h['X-Auth-Token'] = session_data[:body][:access][:token][:id]
+
+ elsif keystone_v3_style_session_data?
+ h['X-Auth-Token'] = session_data[:headers]['x-subject-token']
+
+ else
+ raise "Unknown session data format!"
+
+ end
+ end
+ end
+
+
+ private
+
+ def base_url
+ if session_data[:base_url]
+ session_data[:base_url]
+
+ elsif keystone_v2_style_session_data? && keystone_v2_style_service_info?
+ extract_base_url_from_keystone_v2_session_data
+
+ elsif keystone_v3_style_session_data? && keystone_v3_style_service_info?
+ extract_base_url_from_keystone_v3_session_data
+
+ elsif session_data[:auth_service] && session_data[:auth_service][:host_uri] && session_data[:auth_service][:api_version]
+ version = session_data[:auth_service][:api_version].to_s == "v2" ? "v2.0" : session_data[:auth_service][:api_version]
+ "#{ session_data[:auth_service][:host_uri] }/#{ version }"
+
+ elsif session_data[:auth_service] && session_data[:auth_service][:host_uri]
+ session_data[:auth_service][:host_uri]
+
+ else
+ raise Aviator::Service::MissingServiceEndpointError.new(service.to_s, self.class)
+ end
+ end
+
+
+ def build_service_type_string
+ api_versions_without_a_suffix = [
+ [:compute, :v2],
+ [:ec2, :v1],
+ [:identity, :v2],
+ [:image, :v1],
+ [:metering, :v1],
+ [:s3, :v1],
+ [:volume, :v1]
+ ]
+
+ if api_versions_without_a_suffix.include? [service, api_version]
+ service.to_s
+ else
+ "#{ service }#{ api_version }"
+ end
+ end
+
+
+ def extract_base_url_from_keystone_v2_session_data
+ service_info = session_data[:body][:access][:serviceCatalog].find{ |s| s[:type] == build_service_type_string }
+ service_info[:endpoints][0]["#{ endpoint_type }URL".to_sym]
+ end
+
+
+ def extract_base_url_from_keystone_v3_session_data
+ service_info = session_data[:body][:token][:catalog].select{ |s| s[:type] == build_service_type_string }
+ endpoints = service_info.find{ |s| s.keys.include? "endpoints" }['endpoints']
+
+ endpoints.find{ |s| s['interface'] == endpoint_type.to_s }['url']
+ end
+
+
+ def keystone_v2_style_service_info?
+ not session_data[:body][:access][:serviceCatalog].find{ |s| s[:type] == build_service_type_string }.nil?
+ end
+
+
+ def keystone_v2_style_session_data?
+ session_data.has_key?(:body) && session_data[:body].has_key?(:access)
+ end
+
+
+ def keystone_v3_style_service_info?
+ not session_data[:body][:token][:catalog].find{ |s| s[:type] == build_service_type_string }.nil?
+ end
+
+
+ def keystone_v3_style_session_data?
+ session_data.has_key?(:headers) && session_data[:headers].has_key?("x-subject-token")
+ end
+
+
+ def params_to_querystring(param_names)
+ filters = []
+
+ param_names.each do |param_name|
+ filters << "#{ param_name }=#{ params[param_name] }" if params[param_name]
+ end
+
+ filters.empty? ? "" : "?#{ filters.join('&') }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :base, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :endpoint_type, :admin
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :base, :inherit => [:openstack, :common, :v0, :public, :base] do
+
+ meta :api_version, :v2
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :base, :inherit => [:openstack, :common, :v0, :public, :base] do
+
+ meta :api_version, :v3
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :confirm_server_resize, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Confirm_Resized_Server-d1e3868.html'
+
+ param :id, :required => true
+
+
+ def body
+ {
+ :confirmResize => nil
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_network, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-compute.html#ext-os-networks'
+
+
+ param :label, :required => true
+ param :bridge, :required => false
+ param :bridge_interface, :required => false
+ param :cidr, :required => false
+ param :cidr_v6, :required => false
+ param :dns1, :required => false
+ param :dns2, :required => false
+ param :gateway, :required => false
+ param :gateway_v6, :required => false
+ param :multi_host, :required => false
+ param :project_id, :required => false
+ param :vlan, :required => false
+
+
+ def body
+ p = {
+ :network => {
+ :label => params[:label]
+ }
+ }
+
+ optional_params.each do |key|
+ p[:network][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/os-networks"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_host_details, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref.html#ext-os-hosts'
+
+ param :host_name, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/os-hosts/#{ params[:host_name] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_hosts, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref.html#ext-os-hosts'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/nova/+bug/1224763'
+
+ param :service, :required => false
+ param :zone, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ url = "#{ base_url }/os-hosts"
+
+ filters = []
+
+ optional_params.each do |param_name|
+ filters << "#{ param_name }=#{ params[param_name] }" if params[param_name]
+ end
+
+ url += "?#{ filters.join('&') }" unless filters.empty?
+
+ url
+ end
+
+ end
+
+end
+
--- /dev/null
+module Aviator
+
+ define_request :list_hypervisors, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-compute-v2-ext.html#ext-os-hypervisors'
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/os-hypervisors"
+ end
+
+ end
+
+end
+
--- /dev/null
+module Aviator
+
+ define_request :lock_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_lock_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :lock => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :migrate_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_migrate_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :migrate => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :reset_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_os-resetState_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+ param :state, :required => true
+
+
+ def body
+ {
+ 'os-resetState' => {
+ 'state' => params[:state]
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :resize_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Resize_Server-d1e3707.html'
+
+ param :id, :required => true
+ param :name, :required => true
+ param :flavorRef, :required => true, :alias => :flavor_ref
+
+
+ def body
+ {
+ :resize => {
+ :name => params[:name],
+ :flavorRef => params[:flavorRef]
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :revert_server_resize do
+
+ meta :provider, :openstack
+ meta :service, :compute
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Revert_Resized_Server-d1e4024.html'
+
+ param :id, :required => true
+
+
+ def body
+ {
+ :revertResize => nil
+ }
+ end
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:body][:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ service_spec = session_data[:body][:access][:serviceCatalog].find{|s| s[:type] == service.to_s }
+ "#{ service_spec[:endpoints][0][:adminURL] }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :unlock_server, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_unlock_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :unlock => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :add_floating_ip, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_os-floating-ips-v2_AddFloatingIP__v2__tenant_id__servers__server_id__action_ext-os-floating-ips.html#POST_os-floating-ips-v2_AddFloatingIP__v2__tenant_id__servers__server_id__action_ext-os-floating-ips-Request'
+
+ param :server_id, :required => true
+ param :address, :required => true
+
+ def body
+ {
+ :addFloatingIp => {
+ :address => params[:address]
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:server_id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :allocate_floating_ip, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_os-floating-ips-v2_AllocateFloatingIP__v2__tenant_id__os-floating-ips_ext-os-floating-ips.html'
+
+ param :pool, :required => false
+
+ def body
+ {
+ :pool => params[:pool]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/os-floating-ips"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :change_admin_password, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Change_Password-d1e3234.html'
+
+ link 'additional spec',
+ 'https://answers.launchpad.net/nova/+question/228462'
+
+ param :adminPass, :required => true, :alias => :admin_pass
+ param :id, :required => true
+
+
+ def body
+ p = {
+ :changePassword => {
+ :adminPass => params[:adminPass]
+ }
+ }
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_image, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Create_Image-d1e4655.html'
+
+ param :id, :required => true
+ param :metadata, :required => false
+ param :name, :required => true
+
+
+ def body
+ p = {
+ :createImage => {
+ :name => params[:name]
+ }
+ }
+
+ [:metadata].each do |key|
+ p[:createImage][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_keypair, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_os-keypairs-v2_createKeypair__v2__tenant_id__os-keypairs_ext-os-keypairs.html'
+
+ param :name, :required => true
+ param :public_key, :required => false
+
+ def body
+ {
+ :keypair => {
+ :name => params[:name],
+ :public_key => params[:public_key],
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/os-keypairs"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/CreateServers.html'
+
+ param :accessIPv4, :required => false, :alias => :access_ipv4
+ param :accessIPv6, :required => false, :alias => :access_ipv6
+ param :adminPass, :required => false, :alias => :admin_pass
+ param :imageRef, :required => true, :alias => :image_ref
+ param :flavorRef, :required => true, :alias => :flavor_ref
+ param :key_name, :required => false
+ param :metadata, :required => false
+ param :name, :required => true
+ param :networks, :required => false
+ param :personality, :required => false
+
+
+ def body
+ p = {
+ :server => {
+ :flavorRef => params[:flavorRef],
+ :imageRef => params[:imageRef],
+ :name => params[:name]
+ }
+ }
+
+ [:adminPass, :metadata, :personality, :networks, :accessIPv4, :accessIPv6, :key_name].each do |key|
+ p[:server][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_image, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Image-d1e4957.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id]}"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :delete_image_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Metadata_Item-d1e5790.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Server-d1e2883.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_server_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Delete_Metadata_Item-d1e5790.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_flavor_details, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Flavor_Details-d1e4317.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/flavors/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_image_details, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Image_Details-d1e4848.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id]}"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :get_image_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Metadata_Item-d1e5507.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_network_details, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-compute.html#ext-os-networks'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/os-networks/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Server_Details-d1e2623.html'
+
+ param :id, :required => true
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_server_metadata_item, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Get_Metadata_Item-d1e5507.html'
+
+
+ param :id, :required => true
+ param :key, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata/#{ params[:key] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_addresses, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Addresses-d1e3014.html'
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Addresses_by_Network-d1e3118.html'
+
+
+ param :id, :required => true
+ param :networkID, :required => false, :alias => :network_id
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ url = "#{ base_url }/servers/#{ params[:id] }/ips"
+ url += "/#{ params[:networkID] }" if params[:networkID]
+ url
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_flavors, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Flavors-d1e4188.html'
+
+ param :details, :required => false
+ param :minDisk, :required => false, :alias => :min_disk
+ param :minRam, :required => false, :alias => :min_ram
+ param :marker, :required => false
+ param :limit, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/flavors"
+ str += "/detail" if params[:details]
+ str += params_to_querystring(optional_params + required_params - [:details])
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_floating_ips, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/GET_os-floating-ips-v2_ListFloatingIPs__v2__tenant_id__os-floating-ips_ext-os-floating-ips.html'
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/os-floating-ips"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_image_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Metadata-d1e5089.html'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_images, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Images-d1e4435.html'
+
+ param :details, :required => false
+ param :server, :required => false
+ param :name, :required => false
+ param :status, :required => false
+ param 'changes-since', :required => false, :alias => :changes_since
+ param :marker, :required => false
+ param :limit, :required => false
+ param :type, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/images"
+ str += "/detail" if params[:details]
+ str += params_to_querystring(optional_params + required_params - [:details])
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :list_keypairs, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/GET_os-keypairs-v2_getKeypair__v2__tenant_id__os-keypairs_ext-os-keypairs.html'
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/os-keypairs"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_networks, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-compute.html#ext-os-networks'
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/os-networks"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_server_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Metadata-d1e5089.html'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_servers, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/List_Servers-d1e2078.html'
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/GET_listServers_v2__tenant_id__servers_compute_servers.html'
+
+ link 'github :issue => getting all servers',
+ 'https://github.com/aviator/aviator/issues/35'
+
+ link 'related mailing list discussion',
+ 'https://lists.launchpad.net/openstack/msg24695.html'
+
+ param :all_tenants, :required => false
+ param :details, :required => false
+ param :flavor, :required => false
+ param :image, :required => false
+ param :limit, :required => false
+ param :marker, :required => false
+ param :server, :required => false
+ param :status, :required => false
+ param 'changes-since', :required => false, :alias => :changes_since
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/servers"
+ str += "/detail" if params[:details]
+
+ filters = []
+
+ (optional_params + required_params - [:details]).each do |param_name|
+ value = param_name == :all_tenants && params[param_name] ? 1 : params[param_name]
+ filters << "#{ param_name }=#{ value }" if value
+ end
+
+ str += "?#{ filters.join('&') }" unless filters.empty?
+
+ str
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :pause_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_pause_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :pause => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :reboot_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Reboot_Server-d1e3371.html'
+
+ param :id, :required => true
+ param :type, :required => false
+
+
+ def body
+ p = {
+ :reboot => {
+ :type => params[:type] || 'SOFT'
+ }
+ }
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :rebuild_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Rebuild_Server-d1e3538.html'
+
+ param :accessIPv4, :required => false, :alias => :access_ipv4
+ param :accessIPv6, :required => false, :alias => :access_ipv6
+ param :adminPass, :required => true, :alias => :admin_pass
+ param :id, :required => true
+ param :imageRef, :required => true, :alias => :image_ref
+ param :metadata, :required => false
+ param :name, :required => true
+ param :personality, :required => false
+
+
+ def body
+ p = {
+ :rebuild => {
+ :adminPass => params[:adminPass],
+ :imageRef => params[:imageRef],
+ :name => params[:name]
+ }
+ }
+
+ [:accessIPv4, :accessIPv6, :metadata, :personality].each do |key|
+ p[:rebuild][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :resume_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_resume_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :resume => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v2/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :set_image_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Create_or_Replace_Metadata-d1e5358.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :set_server_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Create_or_Replace_Metadata-d1e5358.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :suspend_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_suspend_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :suspend => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :unpause_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/POST_unpause_v2__tenant_id__servers__server_id__action_ext-os-admin-actions.html'
+
+ param :id, :required => true
+
+
+ def body
+ { :unpause => nil }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/action"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_image_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Update_Metadata-d1e5208.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/images/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_server, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/ServerUpdate.html'
+
+ param :accessIPv4, :required => false, :alias => :access_ipv4
+ param :accessIPv6, :required => false, :alias => :access_ipv6
+ param :id, :required => true
+ param :name, :required => false
+
+
+ def body
+ p = {
+ :server => { }
+ }
+
+ [:name, :accessIPv4, :accessIPv6].each do |key|
+ p[:server][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_server_metadata, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :compute
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-compute/2/content/Update_Metadata-d1e5208.html'
+
+
+ param :id, :required => true
+ param :metadata, :required => true
+
+
+ def body
+ {
+ :metadata => params[:metadata]
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/servers/#{ params[:id] }/metadata"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :add_role_to_user_on_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/PUT_addRolesToUserOnTenant_v2.0_tenants__tenantId__users__userId__roles_OS-KSADM__roleId__.html'
+
+
+ param :tenant_id, :required => true
+ param :user_id, :required => true
+ param :role_id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ p = params
+ "#{ base_url }/tenants/#{ p[:tenant_id] }/users/#{ p[:user_id] }/roles/OS-KSADM/#{ p[:role_id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/'
+
+
+ param :name, :required => true
+ param :description, :required => true
+ param :enabled, :required => true
+
+
+ def body
+ {
+ :tenant => {
+ :name => params[:name],
+ :description => params[:description],
+ :enabled => params[:enabled]
+ }
+ }
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ "#{ base_url }/tenants"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :create_user do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_addUser_v2.0_users_.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1110435'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1226466'
+
+
+ param :name, :required => true
+ param :password, :required => true
+
+ param :email, :required => false
+ param :enabled, :required => false
+ param :tenantId, :required => false, :alias => :tenant_id
+ param :extras, :required => false
+
+
+ def body
+ p = {
+ :user => {}
+ }
+
+ (required_params + optional_params).each do |key|
+ p[:user][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:body][:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ service_spec = session_data[:body][:access][:serviceCatalog].find{|s| s[:type] == 'identity' }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users"
+ end
+
+ end
+
+end
+
--- /dev/null
+module Aviator
+
+ define_request :delete_role_from_user_on_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/DELETE_deleteRoleFromUserOnTenant_v2.0_tenants__tenantId__users__userId__roles_OS-KSADM__roleId__.html'
+
+
+ param :tenant_id, :required => true
+ param :user_id, :required => true
+ param :role_id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ p = params
+ "#{ base_url }/tenants/#{ p[:tenant_id] }/users/#{ p[:user_id] }/roles/OS-KSADM/#{ p[:role_id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/DELETE_deleteTenant_v2.0_tenants__tenantId__.html'
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ "#{ base_url }/tenants/#{ params[:id]}"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_user do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/DELETE_deleteUser_v2.0_users__userId__.html'
+
+ param :id, :required => true
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:body][:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :delete
+ end
+
+
+ def url
+ service_spec = session_data[:body][:access][:serviceCatalog].find{|s| s[:type] == service.to_s }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users/#{ params[:id]}"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_tenant_by_id, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listUsers_v2.0_users_.html'
+
+
+ param :id, :required => true
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/tenants/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+require 'cgi'
+
+module Aviator
+
+ define_request :get_user, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_getUserByName_v2.0_users__User_Operations.html'
+
+ param :name, :required => true
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ "#{ base_url }/users?name=#{ CGI::escape(params.name) }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_tenants, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listTenants_v2.0_tenants_Tenant_Operations.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1218601'
+
+
+ param :marker, :required => false
+ param :limit, :required => false
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ str = "#{ base_url }/tenants"
+ str += params_to_querystring(optional_params + required_params)
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :list_users do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listUsers_v2.0_users_.html'
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:body][:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ service_spec = session_data[:body][:access][:serviceCatalog].find{|s| s[:type] == 'identity' }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_tenant, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :identity
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_updateTenant_v2.0_tenants__tenantId__.html'
+
+
+ param :id, :required => true
+ param :name, :required => false
+ param :enabled, :required => false
+ param :description, :required => false
+
+
+ def body
+ p = {
+ :tenant => {}
+ }
+
+ [:name, :enabled, :description].each do |key|
+ p[:tenant][key] = params[key] unless params[key].nil?
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/tenants/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_user do
+
+ meta :provider, :openstack
+ meta :service, :identity
+ meta :api_version, :v2
+ meta :endpoint_type, :admin
+
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_updateUser_v2.0_users__userId__.html'
+
+ link 'bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1226475'
+
+ param :id, :required => true
+ param :name, :required => false
+ param :password, :required => false
+ param :email, :required => false
+ param :enabled, :required => false
+ param :tenantId, :required => false, :alias => :tenant_id
+ param :extras, :required => false
+
+
+ def body
+ p = {
+ :user => {}
+ }
+
+ optional_params.each do |key|
+ p[:user][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ h = {}
+
+ unless self.anonymous?
+ h['X-Auth-Token'] = session_data[:body][:access][:token][:id]
+ end
+
+ h
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ service_spec = session_data[:body][:access][:serviceCatalog].find { |s| s[:type] == service.to_s }
+ "#{ service_spec[:endpoints][0][:adminURL] }/users/#{ params[:id] }"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :create_token, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :anonymous, true
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1208607'
+
+
+ param :username, :required => false
+ param :password, :required => false
+ param :tokenId, :required => false, :alias => :token_id
+ param :tenantName, :required => false, :alias => :tenant_name
+ param :tenantId, :required => false, :alias => :tenant_id
+
+
+ def body
+ p = if params[:tokenId]
+ {
+ :auth => {
+ :token => {
+ :id => params[:tokenId]
+ }
+ }
+ }
+ else
+ {
+ :auth => {
+ :passwordCredentials => {
+ :username => params[:username],
+ :password => params[:password]
+ }
+ }
+ }
+ end
+
+ p[:auth][:tenantName] = params[:tenantName] if params[:tenantName]
+ p[:auth][:tenantId] = params[:tenantId] if params[:tenantId]
+
+ p
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ url = session_data[:auth_service][:host_uri]
+ url += '/v2.0' if (URI(url).path =~ /^\/?\w+/).nil?
+ url += "/tokens"
+ end
+
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Aviator
+
+ define_request :list_tenants, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :identity
+
+ link 'documentation',
+ 'http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listTenants_v2.0_tokens_tenants_.html'
+
+ link 'documentation bug',
+ 'https://bugs.launchpad.net/keystone/+bug/1218601'
+
+
+ param :marker, :required => false
+ param :limit, :required => false
+
+
+ def url
+ str = "#{ base_url }/tenants"
+ str += params_to_querystring(optional_params + required_params)
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :identity
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v2.0/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+ # Original work by Stephen Paul Suarez
+ # https://github.com/musashi-dev/aviator/blob/develop/lib/aviator/openstack/identity/v3/public/create_token.rb
+
+ define_request :create_token, :inherit => [:openstack, :common, :v3, :public, :base] do
+
+ meta :anonymous, true
+ meta :service, :identity
+ meta :api_version, :v3
+
+ link 'documentation',
+ 'http://api.openstack.org/api-ref-identity-v3.html#Token_Calls'
+
+ param :domainId, :required => false, :alias => :domain_id
+ param :domainName, :required => false, :alias => :domain_name
+ param :password, :required => false
+ param :tenantId, :required => false, :alias => :tenant_id
+ param :tenantName, :required => false, :alias => :tenant_name
+ param :tokenId, :required => false, :alias => :token_id
+ param :userId, :required => false, :alias => :user_id
+ param :username, :required => false
+
+
+ def body
+ params[:token_id] ? token_auth_body : password_auth_body
+ end
+
+
+ def http_method
+ :post
+ end
+
+
+ def url
+ url = session_data[:auth_service][:host_uri]
+ url += '/v3' if (URI(url).path =~ /^\/?\w+/).nil?
+ url += "/auth/tokens"
+ end
+
+
+ private
+
+ # Removes nil elements from hash
+ # Adapted from http://stackoverflow.com/a/14773555/402145
+ def compact_hash(hash, opts = {})
+ opts[:recurse] ||= true
+ hash.inject({}) do |new_hash, (k,v)|
+ if !v.nil?
+ new_hash[k] = opts[:recurse] && v.kind_of?(Hash) ? compact_hash(v, opts) : v
+ end
+ new_hash
+ end
+ end
+
+
+ def domain_hash
+ compact_hash({
+ :id => params[:domain_id],
+ :name => params[:domain_name]
+ })
+ end
+
+
+ def password_auth_body
+ p = {
+ :auth => {
+ :identity => {
+ :methods => ['password'],
+ :password => {
+ :user => compact_hash({
+ :id => params[:user_id],
+ :name => params[:username],
+ :password => params[:password]
+ })
+ }
+ }
+ }
+ }
+
+ if params[:domain_name] || params[:domain_id]
+ p[:auth][:identity][:password][:user][:domain] = domain_hash
+ end
+
+ if params[:tenant_name] || params[:tenant_id] || params[:domain_name] || params[:domain_id]
+ p[:auth][:scope] = scope_hash
+ end
+ p
+ end
+
+
+ def scope_hash
+ p = {}
+
+ if params[:tenant_name] || params[:tenant_id]
+ p[:project] = compact_hash({
+ :id => params[:tenant_id],
+ :name => params[:tenant_name]
+ })
+ p[:project][:domain] = domain_hash if params[:domain_name] || params[:domain_id]
+
+ elsif params[:domain_name] || params[:domain_id]
+ p[:domain] = domain_hash
+ end
+
+ p
+ end
+
+
+ def token_auth_body
+ p = {
+ :auth => {
+ :identity => {
+ :methods => ['token'],
+ :token => { :id => params[:token_id] }
+ }
+ }
+ }
+ p[:auth][:scope] = scope_hash if params[:tenant_name] || params[:tenant_id]
+ p
+ end
+
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_public_images, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :image
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.openstack.org/api/openstack-image-service/1.1/content/requesting-a-list-of-public-vm-images.html'
+
+ param :name, :required => false
+ param :container_format, :required => false
+ param :disk_format, :required => false
+ param :status, :required => false
+ param :size_min, :required => false
+ param :size_max, :required => false
+ param :sort_key, :required => false
+ param :sort_dir, :required => false
+
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ uri = URI(base_url)
+ url = "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/images"
+
+ filters = []
+
+ optional_params.each do |param_name|
+ filters << "#{ param_name }=#{ params[param_name] }" if params[param_name]
+ end
+
+ url += "?#{ filters.join('&') }" unless filters.empty?
+
+ url
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :image
+ meta :api_version, :v1
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_projects, :inherit => [:openstack, :common, :v2, :admin, :base] do
+
+ meta :service, :metering
+ meta :api_version, :v1
+ meta :endpoint_type, :admin
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/projects"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+module Openstack
+
+ #
+ # <b>Request Options</b>
+ #
+ # The following options may be used in combination with each other when calling
+ # an OpenStack request class
+ #
+ # :api_version => :v2::
+ # Forces Aviator to use the request class for the v2 API. For any other
+ # version, replace :v2 with the desired one. Note that this may throw an
+ # error if no such request class for the given api version exists. If you
+ # want to globally specify the API version to use for a specific service,
+ # declare it in your config file under the correct environment. For example:
+ #
+ # production:
+ # provider: openstack
+ # ...
+ # compute_service:
+ # api_version: v2
+ #
+ # Note that the <tt>:api_version</tt> option overrides whatever is declared in the
+ # configuration.
+ #
+ # :endpoint_type => (:public|:admin)::
+ # This allows you to be specific about the endpoint type in cases where two
+ # request classes under admin and public endpoints of the same service share
+ # the same name. This is true, for example, for the :list_tenants request of
+ # the identity service's v2 API. Its public endpoint will return only the tenants
+ # the user is a member of whereas the admin endpoint will return all tenants
+ # in the system.
+ #
+ # :session_data => Hash::
+ # Under normal situations, you wouldn't need to use this as it is automatically populated
+ # by the Session object provided it is authenticated. The specific use case when you'd
+ # need to set thsi optin is when you want to use Aviator to seed your OpenStack installation.
+ # In such a scenario, you would need to use a service token since no usernames and tenants
+ # would exist yet in the environment. To use a service token with Aviator, you will need to
+ # write something similar to the following example:
+ #
+ # openstack = Aviator::Session.new(:config => { :provider => 'openstack'})
+ #
+ # session_data = {:base_url => 'http://example.com',
+ # :service_token => 'service-token-created-at-openstack-install-time'}
+ #
+ # openstack.request :identity, :create_tenant, :api_version => :v2, :session_data => session_data) do |params|
+ # params.name = 'Tenant A'
+ # params.description = 'First Tenant!'
+ # params.enabled = true
+ # end
+ #
+ # Notice how the above code skips authentication. This is because the service token is
+ # pre-validated and ready for use with any request. Also note how we're providing a <tt>:base_url</tt>
+ # member in our session data. This is necessary since we normally get the service endpoints from
+ # Keystone when we authenticate. Now since we are not authenticating against Keystone, we don't have
+ # that catalogue to begin with. Thus the need to hardcode it in the request.
+ #
+ module Provider
+
+ class MultipleServiceApisError < StandardError
+ def initialize(service, entries, request_name)
+ types = entries.map{|e| e[:type] }.join("\n - ")
+ msg = <<EOF
+Multiple entries for the #{ service } service were found in the api catalog:
+
+ - #{ types }
+
+I'm unable to guess which one it is you want to use. To fix this problem, you'll need to
+do one of two things:
+
+ 1) Indicate in the config file the api version you want to use:
+
+ production:
+ provider: openstack
+ ...
+ #{ service }_service:
+ api_version: v2
+
+ 2) Indicate the api version when you call the request:
+
+ session.#{ service }_service.request :#{ request_name }, :api_version => :v2 { ... }
+
+If you combine the two methods, method #2 will override method #1
+
+EOF
+ super(msg)
+ end
+ end
+
+ class << self
+
+ #:nodoc:
+ def find_request(service, name, session_data, options)
+ service = service.to_s
+ endpoint_type = options[:endpoint_type]
+ endpoint_types = if endpoint_type
+ [endpoint_type.to_s.camelize]
+ else
+ ['Public', 'Admin']
+ end
+
+ namespace = Aviator.const_get('Openstack') \
+ .const_get(service.camelize) \
+ .const_get('Requests')
+
+ if options[:api_version]
+ m = options[:api_version].to_s.match(/(v\d+)\.?\d*/)
+ version = m[1].to_s.camelize unless m.nil?
+ end
+
+ version ||= infer_version(session_data, name, service)
+
+ unless version.nil?
+ version = version.to_s.camelize
+ end
+
+ return nil unless version && namespace.const_defined?(version)
+
+ namespace = namespace.const_get(version, name)
+
+ endpoint_types.each do |endpoint_type|
+ name = name.to_s.camelize
+
+ next unless namespace.const_defined?(endpoint_type)
+ next unless namespace.const_get(endpoint_type).const_defined?(name)
+
+ return namespace.const_get(endpoint_type).const_get(name)
+ end
+
+ nil
+ end
+
+
+ def root_dir
+ Pathname.new(__FILE__).join('..').expand_path
+ end
+
+
+ def request_file_paths(service)
+ Dir.glob(Pathname.new(__FILE__).join(
+ '..',
+ service.to_s,
+ 'requests',
+ '**',
+ '*.rb'
+ ).expand_path
+ )
+ end
+
+
+ private
+
+ def infer_version(session_data, request_name, service)
+ if session_data.has_key?(:auth_service) && session_data[:auth_service][:api_version]
+ session_data[:auth_service][:api_version].to_sym
+
+ elsif session_data.has_key?(:auth_service) && session_data[:auth_service][:host_uri]
+ m = session_data[:auth_service][:host_uri].match(/(v\d+)\.?\d*/)
+ return m[1].to_sym unless m.nil?
+
+ elsif session_data.has_key? :base_url
+ m = session_data[:base_url].match(/(v\d+)\.?\d*/)
+ return m[1].to_sym unless m.nil?
+
+ elsif session_data.has_key?(:body) && session_data[:body].has_key?(:access)
+ service_specs = session_data[:body][:access][:serviceCatalog].select{|s| s[:type].match("#{ service }(v\d+)?") }
+ raise MultipleServiceApisError.new(service, service_specs, request_name) unless service_specs.length <= 1
+ raise Aviator::Service::MissingServiceEndpointError.new(service.to_s, request_name) unless service_specs.length > 0
+ version = service_specs[0][:endpoints][0][:publicURL].match(/(v\d+)\.?\d*/)
+ version ? version[1].to_sym : :v1
+
+ elsif session_data.has_key?(:headers) && session_data[:headers].has_key?("x-subject-token")
+ service_specs = session_data[:body][:token][:catalog].select{|s| s[:type].match("#{ service }(v\d+)?") }
+ raise MultipleServiceApisError.new(service, service_specs, request_name) unless service_specs.length <= 1
+ raise Aviator::Service::MissingServiceEndpointError.new(service.to_s, request_name) unless service_specs.length > 0
+ version = service_specs[0][:endpoints][0][:url].match(/(v\d+)\.?\d*/)
+ version ? version[1].to_sym : :v1
+ end
+ end
+
+ end # class << self
+
+ end # module Provider
+
+end # module Openstack
+end # module Aviator
--- /dev/null
+module Aviator
+
+ define_request :create_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/POST_createVolume_v1__tenant_id__volumes_v1__tenant_id__volumes.html'
+
+ param :display_name, :required => true
+ param :display_description, :required => true
+ param :size, :required => true
+ param :volume_type, :required => false
+ param :availability_zone, :required => false
+ param :snapshot_id, :required => false
+ param :metadata, :required => false
+
+ def body
+ p = {
+ :volume => {
+ :display_name => params[:display_name],
+ :display_description => params[:display_description],
+ :size => params[:size]
+ }
+ }
+
+ optional_params.each do |key|
+ p[:volume][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+ def headers
+ super
+ end
+
+ def http_method
+ :post
+ end
+
+ def url
+ "#{ base_url }/volumes"
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :delete_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/DELETE_deleteVolume_v1__tenant_id__volumes__volume_id__v1__tenant_id__volumes.html'
+
+ param :id, :required => true
+
+ def headers
+ super
+ end
+
+ def http_method
+ :delete
+ end
+
+ def url
+ "#{ base_url }/volumes/#{ params[:id] }"
+ end
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :get_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+ meta :provider, :openstack
+ meta :service, :volume
+ meta :api_version, :v1
+ meta :endpoint_type, :public
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/GET_getVolume_v1__tenant_id__volumes__volume_id__v1__tenant_id__volumes.html'
+
+ param :id, :required => true
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ "#{ base_url }/volumes/#{ params[:id] }"
+ end
+
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_volume_types, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :provider, :openstack
+ meta :service, :volume
+ meta :api_version, :v1
+ meta :endpoint_type, :public
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/GET_getVolumeTypes_v1__tenant_id__types_v1__tenant_id__types.html'
+
+ param :extra_specs, :required => false
+ param :name, :required => false
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ "#{ base_url }/types"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :list_volumes, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/GET_getVolumesSimple_v1__tenant_id__volumes_v1__tenant_id__volumes.html'
+
+ param :all_tenants, :required => false
+ param :details, :required => false
+ param :status, :required => false
+ param :availability_zone, :required => false
+ param :bootable, :required => false
+ param :display_name, :required => false
+ param :display_description, :required => false
+ param :volume_type, :required => false
+ param :snapshot_id, :required => false
+ param :size, :required => false
+
+
+ def headers
+ super
+ end
+
+ def http_method
+ :get
+ end
+
+ def url
+ str = "#{ base_url }/volumes"
+ str += "/detail" if params[:details]
+
+ filters = []
+
+ (optional_params + required_params - [:details]).each do |param_name|
+ value = param_name == :all_tenants && params[param_name] ? 1 : params[param_name]
+ filters << "#{ param_name }=#{ value }" if value
+ end
+
+ str += "?#{ filters.join('&') }" unless filters.empty?
+
+ str
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :root, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :get
+ end
+
+
+ def url
+ uri = URI(base_url)
+ "#{ uri.scheme }://#{ uri.host }:#{ uri.port.to_s }/v1/"
+ end
+
+ end
+
+end
--- /dev/null
+module Aviator
+
+ define_request :update_volume, :inherit => [:openstack, :common, :v2, :public, :base] do
+
+ meta :service, :volume
+ meta :api_version, :v1
+
+ link 'documentation', 'http://docs.rackspace.com/cbs/api/v1.0/cbs-devguide/content/PUT_renameVolume_v1__tenant_id__volumes__volume_id__v1__tenant_id__volumes.html'
+
+ param :id, :required => true
+ param :display_name, :required => false
+ param :display_description, :required => false
+
+
+ def body
+ p = { :volume => {} }
+
+ [:display_name, :display_description].each do |key|
+ p[:volume][key] = params[key] if params[key]
+ end
+
+ p
+ end
+
+
+ def headers
+ super
+ end
+
+
+ def http_method
+ :put
+ end
+
+
+ def url
+ "#{ base_url }/volumes/#{ params[:id] }"
+ end
+
+ end
+
+
+end
--- /dev/null
+module Aviator
+ VERSION = "0.2.1"
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2012 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+# Concatenate together multiple IO objects into a single, composite IO object
+# for purposes of reading as a single stream.
+#
+# Usage:
+#
+# crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three'))
+# puts crio.read # => "onetwothree"
+#
+class CompositeReadIO
+ # Create a new composite-read IO from the arguments, all of which should
+ # respond to #read in a manner consistent with IO.
+ def initialize(*ios)
+ @ios = ios.flatten
+ @index = 0
+ end
+
+ # Read from IOs in order until `length` bytes have been received.
+ def read(length = nil, outbuf = nil)
+ got_result = false
+ outbuf = outbuf ? outbuf.replace("") : ""
+
+ while io = current_io
+ if result = io.read(length)
+ got_result ||= !result.nil?
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
+ outbuf << result
+ length -= result.length if length
+ break if length == 0
+ end
+ advance_io
+ end
+ (!got_result && length) ? nil : outbuf
+ end
+
+ def rewind
+ @ios.each { |io| io.rewind }
+ @index = 0
+ end
+
+ private
+
+ def current_io
+ @ios[@index]
+ end
+
+ def advance_io
+ @index += 1
+ end
+end
+
+# Convenience methods for dealing with files and IO that are to be uploaded.
+class UploadIO
+ # Create an upload IO suitable for including in the params hash of a
+ # Net::HTTP::Post::Multipart.
+ #
+ # Can take two forms. The first accepts a filename and content type, and
+ # opens the file for reading (to be closed by finalizer).
+ #
+ # The second accepts an already-open IO, but also requires a third argument,
+ # the filename from which it was opened (particularly useful/recommended if
+ # uploading directly from a form in a framework, which often save the file to
+ # an arbitrarily named RackMultipart file in /tmp).
+ #
+ # Usage:
+ #
+ # UploadIO.new("file.txt", "text/plain")
+ # UploadIO.new(file_io, "text/plain", "file.txt")
+ #
+ attr_reader :content_type, :original_filename, :local_path, :io, :opts
+
+ def initialize(filename_or_io, content_type, filename = nil, opts = {})
+ io = filename_or_io
+ local_path = ""
+ if io.respond_to? :read
+ # in Ruby 1.9.2, StringIOs no longer respond to path
+ # (since they respond to :length, so we don't need their local path, see parts.rb:41)
+ local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
+ else
+ io = File.open(filename_or_io)
+ local_path = filename_or_io
+ end
+ filename ||= local_path
+
+ @content_type = content_type
+ @original_filename = File.basename(filename)
+ @local_path = local_path
+ @io = io
+ @opts = opts
+ end
+
+ def self.convert!(io, content_type, original_filename, local_path)
+ raise ArgumentError, "convert! has been removed. You must now wrap IOs using:\nUploadIO.new(filename_or_io, content_type, filename=nil)\nPlease update your code."
+ end
+
+ def method_missing(*args)
+ @io.send(*args)
+ end
+
+ def respond_to?(meth, include_all = false)
+ @io.respond_to?(meth, include_all) || super(meth, include_all)
+ end
+end
--- /dev/null
+require 'thread'
+require 'cgi'
+require 'set'
+require 'forwardable'
+
+# Public: This is the main namespace for Faraday. You can either use it to
+# create Faraday::Connection objects, or access it directly.
+#
+# Examples
+#
+# Faraday.get "http://faraday.com"
+#
+# conn = Faraday.new "http://faraday.com"
+# conn.get '/'
+#
+module Faraday
+ VERSION = "0.9.0"
+
+ class << self
+ # Public: Gets or sets the root path that Faraday is being loaded from.
+ # This is the root from where the libraries are auto-loaded from.
+ attr_accessor :root_path
+
+ # Public: Gets or sets the path that the Faraday libs are loaded from.
+ attr_accessor :lib_path
+
+ # Public: Gets or sets the Symbol key identifying a default Adapter to use
+ # for the default Faraday::Connection.
+ attr_reader :default_adapter
+
+ # Public: Sets the default Faraday::Connection for simple scripts that
+ # access the Faraday constant directly.
+ #
+ # Faraday.get "https://faraday.com"
+ attr_writer :default_connection
+
+ # Public: Sets the default options used when calling Faraday#new.
+ attr_writer :default_connection_options
+
+ # Public: Initializes a new Faraday::Connection.
+ #
+ # url - The optional String base URL to use as a prefix for all
+ # requests. Can also be the options Hash.
+ # options - The optional Hash used to configure this Faraday::Connection.
+ # Any of these values will be set on every request made, unless
+ # overridden for a specific request.
+ # :url - String base URL.
+ # :params - Hash of URI query unencoded key/value pairs.
+ # :headers - Hash of unencoded HTTP header key/value pairs.
+ # :request - Hash of request options.
+ # :ssl - Hash of SSL options.
+ # :proxy - Hash of Proxy options.
+ #
+ # Examples
+ #
+ # Faraday.new 'http://faraday.com'
+ #
+ # # http://faraday.com?page=1
+ # Faraday.new 'http://faraday.com', :params => {:page => 1}
+ #
+ # # same
+ #
+ # Faraday.new :url => 'http://faraday.com',
+ # :params => {:page => 1}
+ #
+ # Returns a Faraday::Connection.
+ def new(url = nil, options = nil)
+ block = block_given? ? Proc.new : nil
+ options = options ? default_connection_options.merge(options) : default_connection_options.dup
+ Faraday::Connection.new(url, options, &block)
+ end
+
+ # Internal: Requires internal Faraday libraries.
+ #
+ # *libs - One or more relative String names to Faraday classes.
+ #
+ # Returns nothing.
+ def require_libs(*libs)
+ libs.each do |lib|
+ require "#{lib_path}/#{lib}"
+ end
+ end
+
+ # Public: Updates default adapter while resetting
+ # #default_connection.
+ #
+ # Returns the new default_adapter.
+ def default_adapter=(adapter)
+ @default_connection = nil
+ @default_adapter = adapter
+ end
+
+ alias require_lib require_libs
+
+ private
+ # Internal: Proxies method calls on the Faraday constant to
+ # #default_connection.
+ def method_missing(name, *args, &block)
+ default_connection.send(name, *args, &block)
+ end
+ end
+
+ self.root_path = File.expand_path "..", __FILE__
+ self.lib_path = File.expand_path "../faraday", __FILE__
+ self.default_adapter = :net_http
+
+ # Gets the default connection used for simple scripts.
+ #
+ # Returns a Faraday::Connection, configured with the #default_adapter.
+ def self.default_connection
+ @default_connection ||= Connection.new
+ end
+
+ # Gets the default connection options used when calling Faraday#new.
+ #
+ # Returns a Faraday::ConnectionOptions.
+ def self.default_connection_options
+ @default_connection_options ||= ConnectionOptions.new
+ end
+
+ if (!defined?(RUBY_ENGINE) || "ruby" == RUBY_ENGINE) && RUBY_VERSION < '1.9'
+ begin
+ require 'system_timer'
+ Timer = SystemTimer
+ rescue LoadError
+ warn "Faraday: you may want to install system_timer for reliable timeouts"
+ end
+ end
+
+ unless const_defined? :Timer
+ require 'timeout'
+ Timer = Timeout
+ end
+
+ # Public: Adds the ability for other modules to register and lookup
+ # middleware classes.
+ module MiddlewareRegistry
+ # Public: Register middleware class(es) on the current module.
+ #
+ # mapping - A Hash mapping Symbol keys to classes. Classes can be expressed
+ # as fully qualified constant, or a Proc that will be lazily
+ # called to return the former.
+ #
+ # Examples
+ #
+ # module Faraday
+ # class Whatever
+ # # Middleware looked up by :foo returns Faraday::Whatever::Foo.
+ # register_middleware :foo => Foo
+ #
+ # # Middleware looked up by :bar returns Faraday::Whatever.const_get(:Bar)
+ # register_middleware :bar => :Bar
+ #
+ # # Middleware looked up by :baz requires 'baz' and returns Faraday::Whatever.const_get(:Baz)
+ # register_middleware :baz => [:Baz, 'baz']
+ # end
+ # end
+ #
+ # Returns nothing.
+ def register_middleware(autoload_path = nil, mapping = nil)
+ if mapping.nil?
+ mapping = autoload_path
+ autoload_path = nil
+ end
+ middleware_mutex do
+ @middleware_autoload_path = autoload_path if autoload_path
+ (@registered_middleware ||= {}).update(mapping)
+ end
+ end
+
+ # Public: Lookup middleware class with a registered Symbol shortcut.
+ #
+ # key - The Symbol key for the registered middleware.
+ #
+ # Examples
+ #
+ # module Faraday
+ # class Whatever
+ # register_middleware :foo => Foo
+ # end
+ # end
+ #
+ # Faraday::Whatever.lookup_middleware(:foo)
+ # # => Faraday::Whatever::Foo
+ #
+ # Returns a middleware Class.
+ def lookup_middleware(key)
+ load_middleware(key) ||
+ raise(Faraday::Error.new("#{key.inspect} is not registered on #{self}"))
+ end
+
+ def middleware_mutex(&block)
+ @middleware_mutex ||= begin
+ require 'monitor'
+ Monitor.new
+ end
+ @middleware_mutex.synchronize(&block)
+ end
+
+ def fetch_middleware(key)
+ defined?(@registered_middleware) && @registered_middleware[key]
+ end
+
+ def load_middleware(key)
+ value = fetch_middleware(key)
+ case value
+ when Module
+ value
+ when Symbol, String
+ middleware_mutex do
+ @registered_middleware[key] = const_get(value)
+ end
+ when Proc
+ middleware_mutex do
+ @registered_middleware[key] = value.call
+ end
+ when Array
+ middleware_mutex do
+ const, path = value
+ if root = @middleware_autoload_path
+ path = "#{root}/#{path}"
+ end
+ require(path)
+ @registered_middleware[key] = const
+ end
+ load_middleware(key)
+ end
+ end
+ end
+
+ def self.const_missing(name)
+ if name.to_sym == :Builder
+ warn "Faraday::Builder is now Faraday::RackBuilder."
+ const_set name, RackBuilder
+ else
+ super
+ end
+ end
+
+ require_libs "utils", "options", "connection", "rack_builder", "parameters",
+ "middleware", "adapter", "request", "response", "upload_io", "error"
+
+ if !ENV["FARADAY_NO_AUTOLOAD"]
+ require_lib 'autoload'
+ end
+end
+
+# not pulling in active-support JUST for this method. And I love this method.
+class Object
+ # The primary purpose of this method is to "tap into" a method chain,
+ # in order to perform operations on intermediate results within the chain.
+ #
+ # Examples
+ #
+ # (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
+ # tap { |x| puts "array: #{x.inspect}" }.
+ # select { |x| x%2 == 0 }.
+ # tap { |x| puts "evens: #{x.inspect}" }.
+ # map { |x| x*x }.
+ # tap { |x| puts "squares: #{x.inspect}" }
+ #
+ # Yields self.
+ # Returns self.
+ def tap
+ yield(self)
+ self
+ end unless Object.respond_to?(:tap)
+end
--- /dev/null
+module Faraday
+ # Public: This is a base class for all Faraday adapters. Adapters are
+ # responsible for fulfilling a Faraday request.
+ class Adapter < Middleware
+ CONTENT_LENGTH = 'Content-Length'.freeze
+
+ register_middleware File.expand_path('../adapter', __FILE__),
+ :test => [:Test, 'test'],
+ :net_http => [:NetHttp, 'net_http'],
+ :net_http_persistent => [:NetHttpPersistent, 'net_http_persistent'],
+ :typhoeus => [:Typhoeus, 'typhoeus'],
+ :patron => [:Patron, 'patron'],
+ :em_synchrony => [:EMSynchrony, 'em_synchrony'],
+ :em_http => [:EMHttp, 'em_http'],
+ :excon => [:Excon, 'excon'],
+ :rack => [:Rack, 'rack'],
+ :httpclient => [:HTTPClient, 'httpclient']
+
+ # Public: This module marks an Adapter as supporting parallel requests.
+ module Parallelism
+ attr_writer :supports_parallel
+ def supports_parallel?() @supports_parallel end
+
+ def inherited(subclass)
+ super
+ subclass.supports_parallel = self.supports_parallel?
+ end
+ end
+
+ extend Parallelism
+ self.supports_parallel = false
+
+ def call(env)
+ env.clear_body if env.needs_body?
+ end
+
+ def save_response(env, status, body, headers = nil)
+ env.status = status
+ env.body = body
+ env.response_headers = Utils::Headers.new.tap do |response_headers|
+ response_headers.update headers unless headers.nil?
+ yield(response_headers) if block_given?
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ # EventMachine adapter is useful for either asynchronous requests
+ # when in EM reactor loop or for making parallel requests in
+ # synchronous code.
+ class EMHttp < Faraday::Adapter
+ module Options
+ def connection_config(env)
+ options = {}
+ configure_proxy(options, env)
+ configure_timeout(options, env)
+ configure_socket(options, env)
+ configure_ssl(options, env)
+ options
+ end
+
+ def request_config(env)
+ options = {
+ :body => read_body(env),
+ :head => env[:request_headers],
+ # :keepalive => true,
+ # :file => 'path/to/file', # stream data off disk
+ }
+ configure_compression(options, env)
+ options
+ end
+
+ def read_body(env)
+ body = env[:body]
+ body.respond_to?(:read) ? body.read : body
+ end
+
+ def configure_proxy(options, env)
+ if proxy = request_options(env)[:proxy]
+ options[:proxy] = {
+ :host => proxy[:uri].host,
+ :port => proxy[:uri].port,
+ :authorization => [proxy[:user], proxy[:password]]
+ }
+ end
+ end
+
+ def configure_socket(options, env)
+ if bind = request_options(env)[:bind]
+ options[:bind] = {
+ :host => bind[:host],
+ :port => bind[:port]
+ }
+ end
+ end
+
+ def configure_ssl(options, env)
+ if env[:url].scheme == 'https' && env[:ssl]
+ options[:ssl] = {
+ :cert_chain_file => env[:ssl][:ca_file],
+ :verify_peer => env[:ssl].fetch(:verify, true)
+ }
+ end
+ end
+
+ def configure_timeout(options, env)
+ timeout, open_timeout = request_options(env).values_at(:timeout, :open_timeout)
+ options[:connect_timeout] = options[:inactivity_timeout] = timeout
+ options[:connect_timeout] = open_timeout if open_timeout
+ end
+
+ def configure_compression(options, env)
+ if env[:method] == :get and not options[:head].key? 'accept-encoding'
+ options[:head]['accept-encoding'] = 'gzip, compressed'
+ end
+ end
+
+ def request_options(env)
+ env[:request]
+ end
+ end
+
+ include Options
+
+ dependency 'em-http'
+
+ self.supports_parallel = true
+
+ def self.setup_parallel_manager(options = nil)
+ Manager.new
+ end
+
+ def call(env)
+ super
+ perform_request env
+ @app.call env
+ end
+
+ def perform_request(env)
+ if parallel?(env)
+ manager = env[:parallel_manager]
+ manager.add {
+ perform_single_request(env).
+ callback { env[:response].finish(env) }
+ }
+ else
+ unless EventMachine.reactor_running?
+ error = nil
+ # start EM, block until request is completed
+ EventMachine.run do
+ perform_single_request(env).
+ callback { EventMachine.stop }.
+ errback { |client|
+ error = error_message(client)
+ EventMachine.stop
+ }
+ end
+ raise_error(error) if error
+ else
+ # EM is running: instruct upstream that this is an async request
+ env[:parallel_manager] = true
+ perform_single_request(env).
+ callback { env[:response].finish(env) }.
+ errback {
+ # TODO: no way to communicate the error in async mode
+ raise NotImplementedError
+ }
+ end
+ end
+ rescue EventMachine::Connectify::CONNECTError => err
+ if err.message.include?("Proxy Authentication Required")
+ raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Error::ConnectionFailed, err
+ end
+ rescue => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise
+ end
+ end
+
+ # TODO: reuse the connection to support pipelining
+ def perform_single_request(env)
+ req = EventMachine::HttpRequest.new(env[:url], connection_config(env))
+ req.setup_request(env[:method], request_config(env)).callback { |client|
+ save_response(env, client.response_header.status, client.response) do |resp_headers|
+ client.response_header.each do |name, value|
+ resp_headers[name.to_sym] = value
+ end
+ end
+ }
+ end
+
+ def error_message(client)
+ client.error or "request failed"
+ end
+
+ def raise_error(msg)
+ errklass = Faraday::Error::ClientError
+ if msg == Errno::ETIMEDOUT
+ errklass = Faraday::Error::TimeoutError
+ msg = "request timed out"
+ elsif msg == Errno::ECONNREFUSED
+ errklass = Faraday::Error::ConnectionFailed
+ msg = "connection refused"
+ elsif msg == "connection closed by server"
+ errklass = Faraday::Error::ConnectionFailed
+ end
+ raise errklass, msg
+ end
+
+ def parallel?(env)
+ !!env[:parallel_manager]
+ end
+
+ # The parallel manager is designed to start an EventMachine loop
+ # and block until all registered requests have been completed.
+ class Manager
+ def initialize
+ reset
+ end
+
+ def reset
+ @registered_procs = []
+ @num_registered = 0
+ @num_succeeded = 0
+ @errors = []
+ @running = false
+ end
+
+ def running?() @running end
+
+ def add
+ if running?
+ perform_request { yield }
+ else
+ @registered_procs << Proc.new
+ end
+ @num_registered += 1
+ end
+
+ def run
+ if @num_registered > 0
+ @running = true
+ EventMachine.run do
+ @registered_procs.each do |proc|
+ perform_request(&proc)
+ end
+ end
+ if @errors.size > 0
+ raise Faraday::Error::ClientError, @errors.first || "connection failed"
+ end
+ end
+ ensure
+ reset
+ end
+
+ def perform_request
+ client = yield
+ client.callback { @num_succeeded += 1; check_finished }
+ client.errback { @errors << client.error; check_finished }
+ end
+
+ def check_finished
+ if @num_succeeded + @errors.size == @num_registered
+ EventMachine.stop
+ end
+ end
+ end
+ end
+ end
+end
+
+begin
+ require 'openssl'
+rescue LoadError
+ warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support"
+else
+ require 'faraday/adapter/em_http_ssl_patch'
+end if Faraday::Adapter::EMHttp.loaded?
--- /dev/null
+require 'openssl'
+require 'em-http'
+
+module EmHttpSslPatch
+ def ssl_verify_peer(cert_string)
+ cert = nil
+ begin
+ cert = OpenSSL::X509::Certificate.new(cert_string)
+ rescue OpenSSL::X509::CertificateError
+ return false
+ end
+
+ @last_seen_cert = cert
+
+ if certificate_store.verify(@last_seen_cert)
+ begin
+ certificate_store.add_cert(@last_seen_cert)
+ rescue OpenSSL::X509::StoreError => e
+ raise e unless e.message == 'cert already in hash table'
+ end
+ true
+ else
+ raise OpenSSL::SSL::SSLError.new(%(unable to verify the server certificate for "#{host}"))
+ end
+ end
+
+ def ssl_handshake_completed
+ return true unless verify_peer?
+
+ unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, host)
+ raise OpenSSL::SSL::SSLError.new(%(host "#{host}" does not match the server certificate))
+ else
+ true
+ end
+ end
+
+ def verify_peer?
+ parent.connopts.tls[:verify_peer]
+ end
+
+ def host
+ parent.connopts.host
+ end
+
+ def certificate_store
+ @certificate_store ||= begin
+ store = OpenSSL::X509::Store.new
+ store.set_default_paths
+ ca_file = parent.connopts.tls[:cert_chain_file]
+ store.add_file(ca_file) if ca_file
+ store
+ end
+ end
+end
+
+EventMachine::HttpStubConnection.send(:include, EmHttpSslPatch)
--- /dev/null
+require 'uri'
+
+module Faraday
+ class Adapter
+ class EMSynchrony < Faraday::Adapter
+ include EMHttp::Options
+
+ dependency do
+ require 'em-synchrony/em-http'
+ require 'em-synchrony/em-multi'
+ require 'fiber'
+ end
+
+ self.supports_parallel = true
+
+ def self.setup_parallel_manager(options = {})
+ ParallelManager.new
+ end
+
+ def call(env)
+ super
+ request = EventMachine::HttpRequest.new(Utils::URI(env[:url].to_s), connection_config(env))
+
+ http_method = env[:method].to_s.downcase.to_sym
+
+ # Queue requests for parallel execution.
+ if env[:parallel_manager]
+ env[:parallel_manager].add(request, http_method, request_config(env)) do |resp|
+ save_response(env, resp.response_header.status, resp.response) do |resp_headers|
+ resp.response_header.each do |name, value|
+ resp_headers[name.to_sym] = value
+ end
+ end
+
+ # Finalize the response object with values from `env`.
+ env[:response].finish(env)
+ end
+
+ # Execute single request.
+ else
+ client = nil
+ block = lambda { request.send(http_method, request_config(env)) }
+
+ if !EM.reactor_running?
+ EM.run do
+ Fiber.new {
+ client = block.call
+ EM.stop
+ }.resume
+ end
+ else
+ client = block.call
+ end
+
+ raise client.error if client.error
+
+ save_response(env, client.response_header.status, client.response) do |resp_headers|
+ client.response_header.each do |name, value|
+ resp_headers[name.to_sym] = value
+ end
+ end
+ end
+
+ @app.call env
+ rescue Errno::ECONNREFUSED
+ raise Error::ConnectionFailed, $!
+ rescue EventMachine::Connectify::CONNECTError => err
+ if err.message.include?("Proxy Authentication Required")
+ raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Error::ConnectionFailed, err
+ end
+ rescue => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise
+ end
+ end
+ end
+ end
+end
+
+require 'faraday/adapter/em_synchrony/parallel_manager'
+
+begin
+ require 'openssl'
+rescue LoadError
+ warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support"
+else
+ require 'faraday/adapter/em_http_ssl_patch'
+end if Faraday::Adapter::EMSynchrony.loaded?
--- /dev/null
+module Faraday
+ class Adapter
+ class EMSynchrony < Faraday::Adapter
+ class ParallelManager
+
+ # Add requests to queue. The `request` argument should be a
+ # `EM::HttpRequest` object.
+ def add(request, method, *args, &block)
+ queue << {
+ :request => request,
+ :method => method,
+ :args => args,
+ :block => block
+ }
+ end
+
+ # Run all requests on queue with `EM::Synchrony::Multi`, wrapping
+ # it in a reactor and fiber if needed.
+ def run
+ result = nil
+ if !EM.reactor_running?
+ EM.run {
+ Fiber.new do
+ result = perform
+ EM.stop
+ end.resume
+ }
+ else
+ result = perform
+ end
+ result
+ end
+
+
+ private
+
+ # The request queue.
+ def queue
+ @queue ||= []
+ end
+
+ # Main `EM::Synchrony::Multi` performer.
+ def perform
+ multi = ::EM::Synchrony::Multi.new
+
+ queue.each do |item|
+ method = "a#{item[:method]}".to_sym
+
+ req = item[:request].send(method, *item[:args])
+ req.callback(&item[:block])
+
+ req_name = "req_#{multi.requests.size}".to_sym
+ multi.add(req_name, req)
+ end
+
+ # Clear the queue, so parallel manager objects can be reused.
+ @queue = []
+
+ # Block fiber until all requests have returned.
+ multi.perform
+ end
+
+ end # ParallelManager
+ end # EMSynchrony
+ end # Adapter
+end # Faraday
--- /dev/null
+module Faraday
+ class Adapter
+ class Excon < Faraday::Adapter
+ dependency 'excon'
+
+ def initialize(app, connection_options = {})
+ @connection_options = connection_options
+ super(app)
+ end
+
+ def call(env)
+ super
+
+ opts = {}
+ if env[:url].scheme == 'https' && ssl = env[:ssl]
+ opts[:ssl_verify_peer] = !!ssl.fetch(:verify, true)
+ opts[:ssl_ca_path] = ssl[:ca_path] if ssl[:ca_path]
+ opts[:ssl_ca_file] = ssl[:ca_file] if ssl[:ca_file]
+ opts[:client_cert] = ssl[:client_cert] if ssl[:client_cert]
+ opts[:client_key] = ssl[:client_key] if ssl[:client_key]
+ opts[:certificate] = ssl[:certificate] if ssl[:certificate]
+ opts[:private_key] = ssl[:private_key] if ssl[:private_key]
+
+ # https://github.com/geemus/excon/issues/106
+ # https://github.com/jruby/jruby-ossl/issues/19
+ opts[:nonblock] = false
+ end
+
+ if ( req = env[:request] )
+ if req[:timeout]
+ opts[:read_timeout] = req[:timeout]
+ opts[:connect_timeout] = req[:timeout]
+ opts[:write_timeout] = req[:timeout]
+ end
+
+ if req[:open_timeout]
+ opts[:connect_timeout] = req[:open_timeout]
+ opts[:write_timeout] = req[:open_timeout]
+ end
+
+ if req[:proxy]
+ opts[:proxy] = {
+ :host => req[:proxy][:uri].host,
+ :port => req[:proxy][:uri].port,
+ :scheme => req[:proxy][:uri].scheme,
+ :user => req[:proxy][:user],
+ :password => req[:proxy][:password]
+ }
+ end
+ end
+
+ conn = ::Excon.new(env[:url].to_s, opts.merge(@connection_options))
+
+ resp = conn.request \
+ :method => env[:method].to_s.upcase,
+ :headers => env[:request_headers],
+ :body => read_body(env)
+
+ save_response(env, resp.status.to_i, resp.body, resp.headers)
+
+ @app.call env
+ rescue ::Excon::Errors::SocketError => err
+ if err.message =~ /\btimeout\b/
+ raise Error::TimeoutError, err
+ elsif err.message =~ /\bcertificate\b/
+ raise Faraday::SSLError, err
+ else
+ raise Error::ConnectionFailed, err
+ end
+ rescue ::Excon::Errors::Timeout => err
+ raise Error::TimeoutError, err
+ end
+
+ # TODO: support streaming requests
+ def read_body(env)
+ env[:body].respond_to?(:read) ? env[:body].read : env[:body]
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ class HTTPClient < Faraday::Adapter
+ dependency 'httpclient'
+
+ def client
+ @client ||= ::HTTPClient.new
+ end
+
+ def call(env)
+ super
+
+ if req = env[:request]
+ if proxy = req[:proxy]
+ configure_proxy proxy
+ end
+
+ if bind = req[:bind]
+ configure_socket bind
+ end
+
+ configure_timeouts req
+ end
+
+ if env[:url].scheme == 'https' && ssl = env[:ssl]
+ configure_ssl ssl
+ end
+
+ # TODO Don't stream yet.
+ # https://github.com/nahi/httpclient/pull/90
+ env[:body] = env[:body].read if env[:body].respond_to? :read
+
+ resp = client.request env[:method], env[:url],
+ :body => env[:body],
+ :header => env[:request_headers]
+
+ save_response env, resp.status, resp.body, resp.headers
+
+ @app.call env
+ rescue ::HTTPClient::TimeoutError
+ raise Faraday::Error::TimeoutError, $!
+ rescue ::HTTPClient::BadResponseError => err
+ if err.message.include?('status 407')
+ raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Faraday::Error::ClientError, $!
+ end
+ rescue Errno::ECONNREFUSED, EOFError
+ raise Faraday::Error::ConnectionFailed, $!
+ rescue => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise
+ end
+ end
+
+ def configure_socket(bind)
+ client.socket_local.host = bind[:host]
+ client.socket_local.port = bind[:port]
+ end
+
+ def configure_proxy(proxy)
+ client.proxy = proxy[:uri]
+ if proxy[:user] && proxy[:password]
+ client.set_proxy_auth proxy[:user], proxy[:password]
+ end
+ end
+
+ def configure_ssl(ssl)
+ ssl_config = client.ssl_config
+
+ ssl_config.add_trust_ca ssl[:ca_file] if ssl[:ca_file]
+ ssl_config.add_trust_ca ssl[:ca_path] if ssl[:ca_path]
+ ssl_config.cert_store = ssl[:cert_store] if ssl[:cert_store]
+ ssl_config.client_cert = ssl[:client_cert] if ssl[:client_cert]
+ ssl_config.client_key = ssl[:client_key] if ssl[:client_key]
+ ssl_config.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
+ ssl_config.verify_mode = ssl_verify_mode(ssl)
+ end
+
+ def configure_timeouts(req)
+ if req[:timeout]
+ client.connect_timeout = req[:timeout]
+ client.receive_timeout = req[:timeout]
+ client.send_timeout = req[:timeout]
+ end
+
+ if req[:open_timeout]
+ client.connect_timeout = req[:open_timeout]
+ client.send_timeout = req[:open_timeout]
+ end
+ end
+
+ def ssl_verify_mode(ssl)
+ ssl[:verify_mode] || begin
+ if ssl.fetch(:verify, true)
+ OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ else
+ OpenSSL::SSL::VERIFY_NONE
+ end
+ end
+ end
+ end
+ end
+end
--- /dev/null
+begin
+ require 'net/https'
+rescue LoadError
+ warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support"
+ require 'net/http'
+end
+require 'zlib'
+
+module Faraday
+ class Adapter
+ class NetHttp < Faraday::Adapter
+ NET_HTTP_EXCEPTIONS = [
+ EOFError,
+ Errno::ECONNABORTED,
+ Errno::ECONNREFUSED,
+ Errno::ECONNRESET,
+ Errno::EHOSTUNREACH,
+ Errno::EINVAL,
+ Errno::ENETUNREACH,
+ Net::HTTPBadResponse,
+ Net::HTTPHeaderSyntaxError,
+ Net::ProtocolError,
+ SocketError,
+ Zlib::GzipFile::Error,
+ ]
+
+ NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
+
+ def call(env)
+ super
+ http = net_http_connection(env)
+ configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
+
+ req = env[:request]
+ http.read_timeout = http.open_timeout = req[:timeout] if req[:timeout]
+ http.open_timeout = req[:open_timeout] if req[:open_timeout]
+
+ begin
+ http_response = perform_request(http, env)
+ rescue *NET_HTTP_EXCEPTIONS => err
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
+ raise Faraday::SSLError, err
+ else
+ raise Error::ConnectionFailed, err
+ end
+ end
+
+ save_response(env, http_response.code.to_i, http_response.body || '') do |response_headers|
+ http_response.each_header do |key, value|
+ response_headers[key] = value
+ end
+ end
+
+ @app.call env
+ rescue Timeout::Error => err
+ raise Faraday::Error::TimeoutError, err
+ end
+
+ def create_request(env)
+ request = Net::HTTPGenericRequest.new \
+ env[:method].to_s.upcase, # request method
+ !!env[:body], # is there request body
+ :head != env[:method], # is there response body
+ env[:url].request_uri, # request uri path
+ env[:request_headers] # request headers
+
+ if env[:body].respond_to?(:read)
+ request.body_stream = env[:body]
+ else
+ request.body = env[:body]
+ end
+ request
+ end
+
+ def perform_request(http, env)
+ if :get == env[:method] and !env[:body]
+ # prefer `get` to `request` because the former handles gzip (ruby 1.9)
+ http.get env[:url].request_uri, env[:request_headers]
+ else
+ http.request create_request(env)
+ end
+ end
+
+ def net_http_connection(env)
+ if proxy = env[:request][:proxy]
+ Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:user], proxy[:password])
+ else
+ Net::HTTP
+ end.new(env[:url].host, env[:url].port)
+ end
+
+ def configure_ssl(http, ssl)
+ http.use_ssl = true
+ http.verify_mode = ssl_verify_mode(ssl)
+ http.cert_store = ssl_cert_store(ssl)
+
+ http.cert = ssl[:client_cert] if ssl[:client_cert]
+ http.key = ssl[:client_key] if ssl[:client_key]
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
+ http.ca_path = ssl[:ca_path] if ssl[:ca_path]
+ http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
+ http.ssl_version = ssl[:version] if ssl[:version]
+ end
+
+ def ssl_cert_store(ssl)
+ return ssl[:cert_store] if ssl[:cert_store]
+ # Use the default cert store by default, i.e. system ca certs
+ cert_store = OpenSSL::X509::Store.new
+ cert_store.set_default_paths
+ cert_store
+ end
+
+ def ssl_verify_mode(ssl)
+ ssl[:verify_mode] || begin
+ if ssl.fetch(:verify, true)
+ OpenSSL::SSL::VERIFY_PEER
+ else
+ OpenSSL::SSL::VERIFY_NONE
+ end
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# Rely on autoloading instead of explicit require; helps avoid the "already
+# initialized constant" warning on Ruby 1.8.7 when NetHttp is refereced below.
+# require 'faraday/adapter/net_http'
+
+module Faraday
+ class Adapter
+ # Experimental adapter for net-http-persistent
+ class NetHttpPersistent < NetHttp
+ dependency 'net/http/persistent'
+
+ def net_http_connection(env)
+ if proxy = env[:request][:proxy]
+ proxy_uri = ::URI::HTTP === proxy[:uri] ? proxy[:uri].dup : ::URI.parse(proxy[:uri].to_s)
+ proxy_uri.user = proxy_uri.password = nil
+ # awful patch for net-http-persistent 2.8 not unescaping user/password
+ (class << proxy_uri; self; end).class_eval do
+ define_method(:user) { proxy[:user] }
+ define_method(:password) { proxy[:password] }
+ end if proxy[:user]
+ end
+ Net::HTTP::Persistent.new 'Faraday', proxy_uri
+ end
+
+ def perform_request(http, env)
+ http.request env[:url], create_request(env)
+ rescue Net::HTTP::Persistent::Error => error
+ if error.message.include? 'Timeout'
+ raise Faraday::Error::TimeoutError, error
+ elsif error.message.include? 'connection refused'
+ raise Faraday::Error::ConnectionFailed, error
+ else
+ raise
+ end
+ end
+
+ def configure_ssl(http, ssl)
+ http.verify_mode = ssl_verify_mode(ssl)
+ http.cert_store = ssl_cert_store(ssl)
+
+ http.certificate = ssl[:client_cert] if ssl[:client_cert]
+ http.private_key = ssl[:client_key] if ssl[:client_key]
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
+ http.ssl_version = ssl[:version] if ssl[:version]
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ class Patron < Faraday::Adapter
+ dependency 'patron'
+
+ def initialize(app, &block)
+ super(app)
+ @block = block
+ end
+
+ def call(env)
+ super
+
+ # TODO: support streaming requests
+ env[:body] = env[:body].read if env[:body].respond_to? :read
+
+ session = @session ||= create_session
+
+ if req = env[:request]
+ session.timeout = session.connect_timeout = req[:timeout] if req[:timeout]
+ session.connect_timeout = req[:open_timeout] if req[:open_timeout]
+
+ if proxy = req[:proxy]
+ proxy_uri = proxy[:uri].dup
+ proxy_uri.user = proxy[:user] && Utils.escape(proxy[:user]).gsub('+', '%20')
+ proxy_uri.password = proxy[:password] && Utils.escape(proxy[:password]).gsub('+', '%20')
+ session.proxy = proxy_uri.to_s
+ end
+ end
+
+ response = begin
+ data = env[:body] ? env[:body].to_s : nil
+ session.request(env[:method], env[:url].to_s, env[:request_headers], :data => data)
+ rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed
+ raise Error::ConnectionFailed, $!
+ end
+
+ save_response(env, response.status, response.body, response.headers)
+
+ @app.call env
+ rescue ::Patron::TimeoutError => err
+ if err.message == "Connection time-out"
+ raise Faraday::Error::ConnectionFailed, err
+ else
+ raise Faraday::Error::TimeoutError, err
+ end
+ rescue ::Patron::Error => err
+ if err.message.include?("code 407")
+ raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ else
+ raise Error::ConnectionFailed, err
+ end
+ end
+
+ if loaded? && defined?(::Patron::Request::VALID_ACTIONS)
+ # HAX: helps but doesn't work completely
+ # https://github.com/toland/patron/issues/34
+ ::Patron::Request::VALID_ACTIONS.tap do |actions|
+ actions << :patch unless actions.include? :patch
+ actions << :options unless actions.include? :options
+ end
+ end
+
+ def create_session
+ session = ::Patron::Session.new
+ session.insecure = true
+ @block.call(session) if @block
+ session
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ # Sends requests to a Rack app.
+ #
+ # Examples
+ #
+ # class MyRackApp
+ # def call(env)
+ # [200, {'Content-Type' => 'text/html'}, ["hello world"]]
+ # end
+ # end
+ #
+ # Faraday.new do |conn|
+ # conn.adapter :rack, MyRackApp.new
+ # end
+ class Rack < Faraday::Adapter
+ dependency 'rack/test'
+
+ # not prefixed with "HTTP_"
+ SPECIAL_HEADERS = %w[ CONTENT_LENGTH CONTENT_TYPE ]
+
+ def initialize(faraday_app, rack_app)
+ super(faraday_app)
+ mock_session = ::Rack::MockSession.new(rack_app)
+ @session = ::Rack::Test::Session.new(mock_session)
+ end
+
+ def call(env)
+ super
+ rack_env = {
+ :method => env[:method],
+ :input => env[:body].respond_to?(:read) ? env[:body].read : env[:body],
+ 'rack.url_scheme' => env[:url].scheme
+ }
+
+ env[:request_headers].each do |name, value|
+ name = name.upcase.tr('-', '_')
+ name = "HTTP_#{name}" unless SPECIAL_HEADERS.include? name
+ rack_env[name] = value
+ end if env[:request_headers]
+
+ timeout = env[:request][:timeout] || env[:request][:open_timeout]
+ response = if timeout
+ Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
+ else
+ execute_request(env, rack_env)
+ end
+
+ save_response(env, response.status, response.body, response.headers)
+ @app.call env
+ end
+
+ def execute_request(env, rack_env)
+ @session.request(env[:url].to_s, rack_env)
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ # test = Faraday::Connection.new do
+ # use Faraday::Adapter::Test do |stub|
+ # stub.get '/nigiri/sake.json' do
+ # [200, {}, 'hi world']
+ # end
+ # end
+ # end
+ #
+ # resp = test.get '/nigiri/sake.json'
+ # resp.body # => 'hi world'
+ #
+ class Test < Faraday::Adapter
+ attr_accessor :stubs
+
+ class Stubs
+ class NotFound < StandardError
+ end
+
+ def initialize
+ # {:get => [Stub, Stub]}
+ @stack, @consumed = {}, {}
+ yield(self) if block_given?
+ end
+
+ def empty?
+ @stack.empty?
+ end
+
+ def match(request_method, path, headers, body)
+ return false if !@stack.key?(request_method)
+ stack = @stack[request_method]
+ consumed = (@consumed[request_method] ||= [])
+
+ if stub = matches?(stack, path, headers, body)
+ consumed << stack.delete(stub)
+ stub
+ else
+ matches?(consumed, path, headers, body)
+ end
+ end
+
+ def get(path, headers = {}, &block)
+ new_stub(:get, path, headers, &block)
+ end
+
+ def head(path, headers = {}, &block)
+ new_stub(:head, path, headers, &block)
+ end
+
+ def post(path, body=nil, headers = {}, &block)
+ new_stub(:post, path, headers, body, &block)
+ end
+
+ def put(path, body=nil, headers = {}, &block)
+ new_stub(:put, path, headers, body, &block)
+ end
+
+ def patch(path, body=nil, headers = {}, &block)
+ new_stub(:patch, path, headers, body, &block)
+ end
+
+ def delete(path, headers = {}, &block)
+ new_stub(:delete, path, headers, &block)
+ end
+
+ def options(path, headers = {}, &block)
+ new_stub(:options, path, headers, &block)
+ end
+
+ # Raises an error if any of the stubbed calls have not been made.
+ def verify_stubbed_calls
+ failed_stubs = []
+ @stack.each do |method, stubs|
+ unless stubs.size == 0
+ failed_stubs.concat(stubs.map {|stub|
+ "Expected #{method} #{stub}."
+ })
+ end
+ end
+ raise failed_stubs.join(" ") unless failed_stubs.size == 0
+ end
+
+ protected
+
+ def new_stub(request_method, path, headers = {}, body=nil, &block)
+ normalized_path = Faraday::Utils.normalize_path(path)
+ (@stack[request_method] ||= []) << Stub.new(normalized_path, headers, body, block)
+ end
+
+ def matches?(stack, path, headers, body)
+ stack.detect { |stub| stub.matches?(path, headers, body) }
+ end
+ end
+
+ class Stub < Struct.new(:path, :params, :headers, :body, :block)
+ def initialize(full, headers, body, block)
+ path, query = full.split('?')
+ params = query ?
+ Faraday::Utils.parse_nested_query(query) :
+ {}
+ super(path, params, headers, body, block)
+ end
+
+ def matches?(request_uri, request_headers, request_body)
+ request_path, request_query = request_uri.split('?')
+ request_params = request_query ?
+ Faraday::Utils.parse_nested_query(request_query) :
+ {}
+ request_path == path &&
+ params_match?(request_params) &&
+ (body.to_s.size.zero? || request_body == body) &&
+ headers_match?(request_headers)
+ end
+
+ def params_match?(request_params)
+ params.keys.all? do |key|
+ request_params[key] == params[key]
+ end
+ end
+
+ def headers_match?(request_headers)
+ headers.keys.all? do |key|
+ request_headers[key] == headers[key]
+ end
+ end
+
+ def to_s
+ "#{path} #{body}"
+ end
+ end
+
+ def initialize(app, stubs=nil, &block)
+ super(app)
+ @stubs = stubs || Stubs.new
+ configure(&block) if block
+ end
+
+ def configure
+ yield(stubs)
+ end
+
+ def call(env)
+ super
+ normalized_path = Faraday::Utils.normalize_path(env[:url])
+ params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder
+
+ if stub = stubs.match(env[:method], normalized_path, env.request_headers, env[:body])
+ env[:params] = (query = env[:url].query) ?
+ params_encoder.decode(query) :
+ {}
+ status, headers, body = stub.block.call(env)
+ save_response(env, status, body, headers)
+ else
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
+ end
+ @app.call(env)
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Adapter
+ class Typhoeus < Faraday::Adapter
+ self.supports_parallel = true
+
+ def self.setup_parallel_manager(options = {})
+ options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
+ end
+
+ dependency 'typhoeus'
+
+ def call(env)
+ super
+ perform_request env
+ @app.call env
+ end
+
+ def perform_request(env)
+ read_body env
+
+ hydra = env[:parallel_manager] || self.class.setup_parallel_manager
+ hydra.queue request(env)
+ hydra.run unless parallel?(env)
+ rescue Errno::ECONNREFUSED
+ raise Error::ConnectionFailed, $!
+ end
+
+ # TODO: support streaming requests
+ def read_body(env)
+ env[:body] = env[:body].read if env[:body].respond_to? :read
+ end
+
+ def request(env)
+ method = env[:method]
+ # For some reason, prevents Typhoeus from using "100-continue".
+ # We want this because Webrick 1.3.1 can't seem to handle it w/ PUT.
+ method = method.to_s.upcase if method == :put
+
+ req = ::Typhoeus::Request.new env[:url].to_s,
+ :method => method,
+ :body => env[:body],
+ :headers => env[:request_headers],
+ :disable_ssl_peer_verification => (env[:ssl] && env[:ssl].disable?)
+
+ configure_ssl req, env
+ configure_proxy req, env
+ configure_timeout req, env
+ configure_socket req, env
+
+ req.on_complete do |resp|
+ if resp.timed_out?
+ if parallel?(env)
+ # TODO: error callback in async mode
+ else
+ raise Faraday::Error::TimeoutError, "request timed out"
+ end
+ end
+
+ case resp.curl_return_code
+ when 0
+ # everything OK
+ when 7
+ raise Error::ConnectionFailed, resp.curl_error_message
+ when 60
+ raise Faraday::SSLError, resp.curl_error_message
+ else
+ raise Error::ClientError, resp.curl_error_message
+ end
+
+ save_response(env, resp.code, resp.body) do |response_headers|
+ response_headers.parse resp.headers
+ end
+ # in async mode, :response is initialized at this point
+ env[:response].finish(env) if parallel?(env)
+ end
+
+ req
+ end
+
+ def configure_ssl(req, env)
+ ssl = env[:ssl]
+
+ req.ssl_version = ssl[:version] if ssl[:version]
+ req.ssl_cert = ssl[:client_cert] if ssl[:client_cert]
+ req.ssl_key = ssl[:client_key] if ssl[:client_key]
+ req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file]
+ req.ssl_capath = ssl[:ca_path] if ssl[:ca_path]
+ end
+
+ def configure_proxy(req, env)
+ proxy = request_options(env)[:proxy]
+ return unless proxy
+
+ req.proxy = "#{proxy[:uri].host}:#{proxy[:uri].port}"
+
+ if proxy[:user] && proxy[:password]
+ req.proxy_username = proxy[:user]
+ req.proxy_password = proxy[:password]
+ end
+ end
+
+ def configure_timeout(req, env)
+ env_req = request_options(env)
+ req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
+ req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
+ end
+
+ def configure_socket(req, env)
+ if bind = request_options(env)[:bind]
+ req.interface = bind[:host]
+ end
+ end
+
+ def request_options(env)
+ env[:request]
+ end
+
+ def parallel?(env)
+ !!env[:parallel_manager]
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Internal: Adds the ability for other modules to manage autoloadable
+ # constants.
+ module AutoloadHelper
+ # Internal: Registers the constants to be auto loaded.
+ #
+ # prefix - The String require prefix. If the path is inside Faraday, then
+ # it will be prefixed with the root path of this loaded Faraday
+ # version.
+ # options - Hash of Symbol => String library names.
+ #
+ # Examples.
+ #
+ # Faraday.autoload_all 'faraday/foo',
+ # :Bar => 'bar'
+ #
+ # # requires faraday/foo/bar to load Faraday::Bar.
+ # Faraday::Bar
+ #
+ #
+ # Returns nothing.
+ def autoload_all(prefix, options)
+ if prefix =~ /^faraday(\/|$)/i
+ prefix = File.join(Faraday.root_path, prefix)
+ end
+ options.each do |const_name, path|
+ autoload const_name, File.join(prefix, path)
+ end
+ end
+
+ # Internal: Loads each autoloaded constant. If thread safety is a concern,
+ # wrap this in a Mutex.
+ #
+ # Returns nothing.
+ def load_autoloaded_constants
+ constants.each do |const|
+ const_get(const) if autoload?(const)
+ end
+ end
+
+ # Internal: Filters the module's contents with those that have been already
+ # autoloaded.
+ #
+ # Returns an Array of Class/Module objects.
+ def all_loaded_constants
+ constants.map { |c| const_get(c) }.
+ select { |a| a.respond_to?(:loaded?) && a.loaded? }
+ end
+ end
+
+ class Adapter
+ extend AutoloadHelper
+ autoload_all 'faraday/adapter',
+ :NetHttp => 'net_http',
+ :NetHttpPersistent => 'net_http_persistent',
+ :Typhoeus => 'typhoeus',
+ :EMSynchrony => 'em_synchrony',
+ :EMHttp => 'em_http',
+ :Patron => 'patron',
+ :Excon => 'excon',
+ :Test => 'test',
+ :Rack => 'rack',
+ :HTTPClient => 'httpclient'
+ end
+
+ class Request
+ extend AutoloadHelper
+ autoload_all 'faraday/request',
+ :UrlEncoded => 'url_encoded',
+ :Multipart => 'multipart',
+ :Retry => 'retry',
+ :Timeout => 'timeout',
+ :Authorization => 'authorization',
+ :BasicAuthentication => 'basic_authentication',
+ :TokenAuthentication => 'token_authentication',
+ :Instrumentation => 'instrumentation'
+ end
+
+ class Response
+ extend AutoloadHelper
+ autoload_all 'faraday/response',
+ :RaiseError => 'raise_error',
+ :Logger => 'logger'
+ end
+end
--- /dev/null
+module Faraday
+ # Public: Connection objects manage the default properties and the middleware
+ # stack for fulfilling an HTTP request.
+ #
+ # Examples
+ #
+ # conn = Faraday::Connection.new 'http://sushi.com'
+ #
+ # # GET http://sushi.com/nigiri
+ # conn.get 'nigiri'
+ # # => #<Faraday::Response>
+ #
+ class Connection
+ # A Set of allowed HTTP verbs.
+ METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
+
+ # Public: Returns a Hash of URI query unencoded key/value pairs.
+ attr_reader :params
+
+ # Public: Returns a Hash of unencoded HTTP header key/value pairs.
+ attr_reader :headers
+
+ # Public: Returns a URI with the prefix used for all requests from this
+ # Connection. This includes a default host name, scheme, port, and path.
+ attr_reader :url_prefix
+
+ # Public: Returns the Faraday::Builder for this Connection.
+ attr_reader :builder
+
+ # Public: Returns a Hash of the request options.
+ attr_reader :options
+
+ # Public: Returns a Hash of the SSL options.
+ attr_reader :ssl
+
+ # Public: Returns the parallel manager for this Connection.
+ attr_reader :parallel_manager
+
+ # Public: Sets the default parallel manager for this connection.
+ attr_writer :default_parallel_manager
+
+ # Public: Initializes a new Faraday::Connection.
+ #
+ # url - URI or String base URL to use as a prefix for all
+ # requests (optional).
+ # options - Hash or Faraday::ConnectionOptions.
+ # :url - URI or String base URL (default: "http:/").
+ # :params - Hash of URI query unencoded key/value pairs.
+ # :headers - Hash of unencoded HTTP header key/value pairs.
+ # :request - Hash of request options.
+ # :ssl - Hash of SSL options.
+ # :proxy - URI, String or Hash of HTTP proxy options
+ # (default: "http_proxy" environment variable).
+ # :uri - URI or String
+ # :user - String (optional)
+ # :password - String (optional)
+ def initialize(url = nil, options = nil)
+ if url.is_a?(Hash)
+ options = ConnectionOptions.from(url)
+ url = options.url
+ else
+ options = ConnectionOptions.from(options)
+ end
+
+ @parallel_manager = nil
+ @headers = Utils::Headers.new
+ @params = Utils::ParamsHash.new
+ @options = options.request
+ @ssl = options.ssl
+ @default_parallel_manager = options.parallel_manager
+
+ @builder = options.builder || begin
+ # pass an empty block to Builder so it doesn't assume default middleware
+ options.new_builder(block_given? ? Proc.new { |b| } : nil)
+ end
+
+ self.url_prefix = url || 'http:/'
+
+ @params.update(options.params) if options.params
+ @headers.update(options.headers) if options.headers
+
+ @proxy = nil
+ proxy(options.fetch(:proxy) {
+ uri = ENV['http_proxy']
+ if uri && !uri.empty?
+ uri = 'http://' + uri if uri !~ /^http/i
+ uri
+ end
+ })
+
+ yield(self) if block_given?
+
+ @headers[:user_agent] ||= "Faraday v#{VERSION}"
+ end
+
+ # Public: Sets the Hash of URI query unencoded key/value pairs.
+ def params=(hash)
+ @params.replace hash
+ end
+
+ # Public: Sets the Hash of unencoded HTTP header key/value pairs.
+ def headers=(hash)
+ @headers.replace hash
+ end
+
+ extend Forwardable
+
+ def_delegators :builder, :build, :use, :request, :response, :adapter, :app
+
+ # Public: Makes an HTTP request without a body.
+ #
+ # url - The optional String base URL to use as a prefix for all
+ # requests. Can also be the options Hash.
+ # params - Hash of URI query unencoded key/value pairs.
+ # headers - Hash of unencoded HTTP header key/value pairs.
+ #
+ # Examples
+ #
+ # conn.get '/items', {:page => 1}, :accept => 'application/json'
+ # conn.head '/items/1'
+ #
+ # # ElasticSearch example sending a body with GET.
+ # conn.get '/twitter/tweet/_search' do |req|
+ # req.headers[:content_type] = 'application/json'
+ # req.params[:routing] = 'kimchy'
+ # req.body = JSON.generate(:query => {...})
+ # end
+ #
+ # Yields a Faraday::Response for further request customizations.
+ # Returns a Faraday::Response.
+ #
+ # Signature
+ #
+ # <verb>(url = nil, params = nil, headers = nil)
+ #
+ # verb - An HTTP verb: get, head, or delete.
+ %w[get head delete].each do |method|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(url = nil, params = nil, headers = nil)
+ run_request(:#{method}, url, nil, headers) { |request|
+ request.params.update(params) if params
+ yield(request) if block_given?
+ }
+ end
+ RUBY
+ end
+
+ # Public: Makes an HTTP request with a body.
+ #
+ # url - The optional String base URL to use as a prefix for all
+ # requests. Can also be the options Hash.
+ # body - The String body for the request.
+ # headers - Hash of unencoded HTTP header key/value pairs.
+ #
+ # Examples
+ #
+ # conn.post '/items', data, :content_type => 'application/json'
+ #
+ # # Simple ElasticSearch indexing sample.
+ # conn.post '/twitter/tweet' do |req|
+ # req.headers[:content_type] = 'application/json'
+ # req.params[:routing] = 'kimchy'
+ # req.body = JSON.generate(:user => 'kimchy', ...)
+ # end
+ #
+ # Yields a Faraday::Response for further request customizations.
+ # Returns a Faraday::Response.
+ #
+ # Signature
+ #
+ # <verb>(url = nil, body = nil, headers = nil)
+ #
+ # verb - An HTTP verb: post, put, or patch.
+ %w[post put patch].each do |method|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(url = nil, body = nil, headers = nil, &block)
+ run_request(:#{method}, url, body, headers, &block)
+ end
+ RUBY
+ end
+
+ # Public: Sets up the Authorization header with these credentials, encoded
+ # with base64.
+ #
+ # login - The authentication login.
+ # pass - The authentication password.
+ #
+ # Examples
+ #
+ # conn.basic_auth 'Aladdin', 'open sesame'
+ # conn.headers['Authorization']
+ # # => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ #
+ # Returns nothing.
+ def basic_auth(login, pass)
+ set_authorization_header(:basic_auth, login, pass)
+ end
+
+ # Public: Sets up the Authorization header with the given token.
+ #
+ # token - The String token.
+ # options - Optional Hash of extra token options.
+ #
+ # Examples
+ #
+ # conn.token_auth 'abcdef', :foo => 'bar'
+ # conn.headers['Authorization']
+ # # => "Token token=\"abcdef\",
+ # foo=\"bar\""
+ #
+ # Returns nothing.
+ def token_auth(token, options = nil)
+ set_authorization_header(:token_auth, token, options)
+ end
+
+ # Public: Sets up a custom Authorization header.
+ #
+ # type - The String authorization type.
+ # token - The String or Hash token. A String value is taken literally, and
+ # a Hash is encoded into comma separated key/value pairs.
+ #
+ # Examples
+ #
+ # conn.authorization :Bearer, 'mF_9.B5f-4.1JqM'
+ # conn.headers['Authorization']
+ # # => "Bearer mF_9.B5f-4.1JqM"
+ #
+ # conn.authorization :Token, :token => 'abcdef', :foo => 'bar'
+ # conn.headers['Authorization']
+ # # => "Token token=\"abcdef\",
+ # foo=\"bar\""
+ #
+ # Returns nothing.
+ def authorization(type, token)
+ set_authorization_header(:authorization, type, token)
+ end
+
+ # Internal: Traverse the middleware stack in search of a
+ # parallel-capable adapter.
+ #
+ # Yields in case of not found.
+ #
+ # Returns a parallel manager or nil if not found.
+ def default_parallel_manager
+ @default_parallel_manager ||= begin
+ handler = @builder.handlers.detect do |h|
+ h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
+ end
+
+ if handler
+ handler.klass.setup_parallel_manager
+ elsif block_given?
+ yield
+ end
+ end
+ end
+
+ # Public: Determine if this Faraday::Connection can make parallel requests.
+ #
+ # Returns true or false.
+ def in_parallel?
+ !!@parallel_manager
+ end
+
+ # Public: Sets up the parallel manager to make a set of requests.
+ #
+ # manager - The parallel manager that this Connection's Adapter uses.
+ #
+ # Yields a block to execute multiple requests.
+ # Returns nothing.
+ def in_parallel(manager = nil)
+ @parallel_manager = manager || default_parallel_manager {
+ warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack"
+ warn caller[2,10].join("\n")
+ nil
+ }
+ yield
+ @parallel_manager && @parallel_manager.run
+ ensure
+ @parallel_manager = nil
+ end
+
+ # Public: Gets or Sets the Hash proxy options.
+ def proxy(arg = nil)
+ return @proxy if arg.nil?
+ @proxy = ProxyOptions.from(arg)
+ end
+
+ def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
+ def_delegator :url_prefix, :path, :path_prefix
+
+ # Public: Parses the giving url with URI and stores the individual
+ # components in this connection. These components serve as defaults for
+ # requests made by this connection.
+ #
+ # url - A String or URI.
+ #
+ # Examples
+ #
+ # conn = Faraday::Connection.new { ... }
+ # conn.url_prefix = "https://sushi.com/api"
+ # conn.scheme # => https
+ # conn.path_prefix # => "/api"
+ #
+ # conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
+ #
+ # Returns the parsed URI from teh given input..
+ def url_prefix=(url, encoder = nil)
+ uri = @url_prefix = Utils.URI(url)
+ self.path_prefix = uri.path
+
+ params.merge_query(uri.query, encoder)
+ uri.query = nil
+
+ with_uri_credentials(uri) do |user, password|
+ basic_auth user, password
+ uri.user = uri.password = nil
+ end
+
+ uri
+ end
+
+ # Public: Sets the path prefix and ensures that it always has a leading
+ # slash.
+ #
+ # value - A String.
+ #
+ # Returns the new String path prefix.
+ def path_prefix=(value)
+ url_prefix.path = if value
+ value = '/' + value unless value[0,1] == '/'
+ value
+ end
+ end
+
+ # Public: Takes a relative url for a request and combines it with the defaults
+ # set on the connection instance.
+ #
+ # conn = Faraday::Connection.new { ... }
+ # conn.url_prefix = "https://sushi.com/api?token=abc"
+ # conn.scheme # => https
+ # conn.path_prefix # => "/api"
+ #
+ # conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
+ # conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
+ #
+ def build_url(url = nil, extra_params = nil)
+ uri = build_exclusive_url(url)
+
+ query_values = params.dup.merge_query(uri.query, options.params_encoder)
+ query_values.update extra_params if extra_params
+ uri.query = query_values.empty? ? nil : query_values.to_query(options.params_encoder)
+
+ uri
+ end
+
+ # Builds and runs the Faraday::Request.
+ #
+ # method - The Symbol HTTP method.
+ # url - The String or URI to access.
+ # body - The String body
+ # headers - Hash of unencoded HTTP header key/value pairs.
+ #
+ # Returns a Faraday::Response.
+ def run_request(method, url, body, headers)
+ if !METHODS.include?(method)
+ raise ArgumentError, "unknown http method: #{method}"
+ end
+
+ request = build_request(method) do |req|
+ req.url(url) if url
+ req.headers.update(headers) if headers
+ req.body = body if body
+ yield(req) if block_given?
+ end
+
+ builder.build_response(self, request)
+ end
+
+ # Creates and configures the request object.
+ #
+ # Returns the new Request.
+ def build_request(method)
+ Request.create(method) do |req|
+ req.params = self.params.dup
+ req.headers = self.headers.dup
+ req.options = self.options.merge(:proxy => self.proxy)
+ yield(req) if block_given?
+ end
+ end
+
+ # Internal: Build an absolute URL based on url_prefix.
+ #
+ # url - A String or URI-like object
+ # params - A Faraday::Utils::ParamsHash to replace the query values
+ # of the resulting url (default: nil).
+ #
+ # Returns the resulting URI instance.
+ def build_exclusive_url(url = nil, params = nil)
+ url = nil if url.respond_to?(:empty?) and url.empty?
+ base = url_prefix
+ if url and base.path and base.path !~ /\/$/
+ base = base.dup
+ base.path = base.path + '/' # ensure trailing slash
+ end
+ uri = url ? base + url : base
+ uri.query = params.to_query(options.params_encoder) if params
+ uri.query = nil if uri.query and uri.query.empty?
+ uri
+ end
+
+ # Internal: Creates a duplicate of this Faraday::Connection.
+ #
+ # Returns a Faraday::Connection.
+ def dup
+ self.class.new(build_exclusive_url, :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
+ end
+
+ # Internal: Yields username and password extracted from a URI if they both exist.
+ def with_uri_credentials(uri)
+ if uri.user and uri.password
+ yield(Utils.unescape(uri.user), Utils.unescape(uri.password))
+ end
+ end
+
+ def set_authorization_header(header_type, *args)
+ header = Faraday::Request.lookup_middleware(header_type).
+ header(*args)
+ headers[Faraday::Request::Authorization::KEY] = header
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Error < StandardError; end
+ class MissingDependency < Error; end
+
+ class ClientError < Error
+ attr_reader :response
+
+ def initialize(ex, response = nil)
+ @wrapped_exception = nil
+ @response = response
+
+ if ex.respond_to?(:backtrace)
+ super(ex.message)
+ @wrapped_exception = ex
+ elsif ex.respond_to?(:each_key)
+ super("the server responded with status #{ex[:status]}")
+ @response = ex
+ else
+ super(ex.to_s)
+ end
+ end
+
+ def backtrace
+ if @wrapped_exception
+ @wrapped_exception.backtrace
+ else
+ super
+ end
+ end
+
+ def inspect
+ %(#<#{self.class}>)
+ end
+ end
+
+ class ConnectionFailed < ClientError; end
+ class ResourceNotFound < ClientError; end
+ class ParsingError < ClientError; end
+
+ class TimeoutError < ClientError
+ def initialize(ex = nil)
+ super(ex || "timeout")
+ end
+ end
+
+ class SSLError < ClientError
+ end
+
+ [:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
+ :ParsingError, :TimeoutError, :SSLError].each do |const|
+ Error.const_set(const, Faraday.const_get(const))
+ end
+end
--- /dev/null
+module Faraday
+ class Middleware
+ extend MiddlewareRegistry
+
+ class << self
+ attr_accessor :load_error
+ private :load_error=
+ end
+
+ self.load_error = nil
+
+ # Executes a block which should try to require and reference dependent libraries
+ def self.dependency(lib = nil)
+ lib ? require(lib) : yield
+ rescue LoadError, NameError => error
+ self.load_error = error
+ end
+
+ def self.new(*)
+ raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
+ super
+ end
+
+ def self.loaded?
+ load_error.nil?
+ end
+
+ def self.inherited(subclass)
+ super
+ subclass.send(:load_error=, self.load_error)
+ end
+
+ def initialize(app = nil)
+ @app = app
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Subclasses Struct with some special helpers for converting from a Hash to
+ # a Struct.
+ class Options < Struct
+ # Public
+ def self.from(value)
+ value ? new.update(value) : new
+ end
+
+ # Public
+ def each
+ return to_enum(:each) unless block_given?
+ members.each do |key|
+ yield(key.to_sym, send(key))
+ end
+ end
+
+ # Public
+ def update(obj)
+ obj.each do |key, value|
+ if sub_options = self.class.options_for(key)
+ value = sub_options.from(value) if value
+ elsif Hash === value
+ hash = {}
+ value.each do |hash_key, hash_value|
+ hash[hash_key] = hash_value
+ end
+ value = hash
+ end
+
+ self.send("#{key}=", value) unless value.nil?
+ end
+ self
+ end
+
+ alias merge! update
+
+ # Public
+ def delete(key)
+ value = send(key)
+ send("#{key}=", nil)
+ value
+ end
+
+ # Public
+ def clear
+ members.each { |member| delete(member) }
+ end
+
+ # Public
+ def merge(value)
+ dup.update(value)
+ end
+
+ # Public
+ def fetch(key, *args)
+ unless symbolized_key_set.include?(key.to_sym)
+ key_setter = "#{key}="
+ if args.size > 0
+ send(key_setter, args.first)
+ elsif block_given?
+ send(key_setter, Proc.new.call(key))
+ else
+ raise self.class.fetch_error_class, "key not found: #{key.inspect}"
+ end
+ end
+ send(key)
+ end
+
+ # Public
+ def values_at(*keys)
+ keys.map { |key| send(key) }
+ end
+
+ # Public
+ def keys
+ members.reject { |member| send(member).nil? }
+ end
+
+ # Public
+ def empty?
+ keys.empty?
+ end
+
+ # Public
+ def each_key
+ return to_enum(:each_key) unless block_given?
+ keys.each do |key|
+ yield(key)
+ end
+ end
+
+ # Public
+ def key?(key)
+ keys.include?(key)
+ end
+
+ alias has_key? key?
+
+ # Public
+ def each_value
+ return to_enum(:each_value) unless block_given?
+ values.each do |value|
+ yield(value)
+ end
+ end
+
+ # Public
+ def value?(value)
+ values.include?(value)
+ end
+
+ alias has_value? value?
+
+ # Public
+ def to_hash
+ hash = {}
+ members.each do |key|
+ value = send(key)
+ hash[key.to_sym] = value unless value.nil?
+ end
+ hash
+ end
+
+ # Internal
+ def inspect
+ values = []
+ members.each do |member|
+ value = send(member)
+ values << "#{member}=#{value.inspect}" if value
+ end
+ values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
+
+ %(#<#{self.class}#{values}>)
+ end
+
+ # Internal
+ def self.options(mapping)
+ attribute_options.update(mapping)
+ end
+
+ # Internal
+ def self.options_for(key)
+ attribute_options[key]
+ end
+
+ # Internal
+ def self.attribute_options
+ @attribute_options ||= {}
+ end
+
+ def self.memoized(key)
+ memoized_attributes[key.to_sym] = Proc.new
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{key}() self[:#{key}]; end
+ RUBY
+ end
+
+ def self.memoized_attributes
+ @memoized_attributes ||= {}
+ end
+
+ def [](key)
+ key = key.to_sym
+ if method = self.class.memoized_attributes[key]
+ super(key) || (self[key] = instance_eval(&method))
+ else
+ super
+ end
+ end
+
+ def symbolized_key_set
+ @symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym })
+ end
+
+ def self.inherited(subclass)
+ super
+ subclass.attribute_options.update(attribute_options)
+ subclass.memoized_attributes.update(memoized_attributes)
+ end
+
+ def self.fetch_error_class
+ @fetch_error_class ||= if Object.const_defined?(:KeyError)
+ ::KeyError
+ else
+ ::IndexError
+ end
+ end
+ end
+
+ class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
+ :timeout, :open_timeout, :boundary,
+ :oauth)
+
+ def []=(key, value)
+ if key && key.to_sym == :proxy
+ super(key, value ? ProxyOptions.from(value) : nil)
+ else
+ super(key, value)
+ end
+ end
+ end
+
+ class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
+ :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth, :version)
+
+ def verify?
+ verify != false
+ end
+
+ def disable?
+ !verify?
+ end
+ end
+
+ class ProxyOptions < Options.new(:uri, :user, :password)
+ extend Forwardable
+ def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
+
+ def self.from(value)
+ case value
+ when String
+ value = {:uri => Utils.URI(value)}
+ when URI
+ value = {:uri => value}
+ when Hash, Options
+ if uri = value.delete(:uri)
+ value[:uri] = Utils.URI(uri)
+ end
+ end
+ super(value)
+ end
+
+ memoized(:user) { uri.user && Utils.unescape(uri.user) }
+ memoized(:password) { uri.password && Utils.unescape(uri.password) }
+ end
+
+ class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
+ :parallel_manager, :params, :headers, :builder_class)
+
+ options :request => RequestOptions, :ssl => SSLOptions
+
+ memoized(:request) { self.class.options_for(:request).new }
+
+ memoized(:ssl) { self.class.options_for(:ssl).new }
+
+ memoized(:builder_class) { RackBuilder }
+
+ def new_builder(block)
+ builder_class.new(&block)
+ end
+ end
+
+ class Env < Options.new(:method, :body, :url, :request, :request_headers,
+ :ssl, :parallel_manager, :params, :response, :response_headers, :status)
+
+ ContentLength = 'Content-Length'.freeze
+ StatusesWithoutBody = Set.new [204, 304]
+ SuccessfulStatuses = 200..299
+
+ # A Set of HTTP verbs that typically send a body. If no body is set for
+ # these requests, the Content-Length header is set to 0.
+ MethodsWithBodies = Set.new [:post, :put, :patch, :options]
+
+ options :request => RequestOptions,
+ :request_headers => Utils::Headers, :response_headers => Utils::Headers
+
+ extend Forwardable
+
+ def_delegators :request, :params_encoder
+
+ # Public
+ def [](key)
+ if in_member_set?(key)
+ super(key)
+ else
+ custom_members[key]
+ end
+ end
+
+ # Public
+ def []=(key, value)
+ if in_member_set?(key)
+ super(key, value)
+ else
+ custom_members[key] = value
+ end
+ end
+
+ # Public
+ def success?
+ SuccessfulStatuses.include?(status)
+ end
+
+ # Public
+ def needs_body?
+ !body && MethodsWithBodies.include?(method)
+ end
+
+ # Public
+ def clear_body
+ request_headers[ContentLength] = '0'
+ self.body = ''
+ end
+
+ # Public
+ def parse_body?
+ !StatusesWithoutBody.include?(status)
+ end
+
+ # Public
+ def parallel?
+ !!parallel_manager
+ end
+
+ def inspect
+ attrs = [nil]
+ members.each do |mem|
+ if value = send(mem)
+ attrs << "@#{mem}=#{value.inspect}"
+ end
+ end
+ if !custom_members.empty?
+ attrs << "@custom=#{custom_members.inspect}"
+ end
+ %(#<#{self.class}#{attrs.join(" ")}>)
+ end
+
+ # Internal
+ def custom_members
+ @custom_members ||= {}
+ end
+
+ # Internal
+ if members.first.is_a?(Symbol)
+ def in_member_set?(key)
+ self.class.member_set.include?(key.to_sym)
+ end
+ else
+ def in_member_set?(key)
+ self.class.member_set.include?(key.to_s)
+ end
+ end
+
+ # Internal
+ def self.member_set
+ @member_set ||= Set.new(members)
+ end
+ end
+end
--- /dev/null
+module Faraday
+ module NestedParamsEncoder
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
+
+ def self.escape(s)
+ return s.to_s.gsub(ESCAPE_RE) {
+ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
+ }.tr(' ', '+')
+ end
+
+ def self.unescape(s)
+ CGI.unescape(s.to_s)
+ end
+
+ def self.encode(params)
+ return nil if params == nil
+
+ if !params.is_a?(Array)
+ if !params.respond_to?(:to_hash)
+ raise TypeError,
+ "Can't convert #{params.class} into Hash."
+ end
+ params = params.to_hash
+ params = params.map do |key, value|
+ key = key.to_s if key.kind_of?(Symbol)
+ [key, value]
+ end
+ # Useful default for OAuth and caching.
+ # Only to be used for non-Array inputs. Arrays should preserve order.
+ params.sort!
+ end
+
+ # Helper lambda
+ to_query = lambda do |parent, value|
+ if value.is_a?(Hash)
+ value = value.map do |key, val|
+ key = escape(key)
+ [key, val]
+ end
+ value.sort!
+ buffer = ""
+ value.each do |key, val|
+ new_parent = "#{parent}%5B#{key}%5D"
+ buffer << "#{to_query.call(new_parent, val)}&"
+ end
+ return buffer.chop
+ elsif value.is_a?(Array)
+ buffer = ""
+ value.each_with_index do |val, i|
+ new_parent = "#{parent}%5B%5D"
+ buffer << "#{to_query.call(new_parent, val)}&"
+ end
+ return buffer.chop
+ else
+ encoded_value = escape(value)
+ return "#{parent}=#{encoded_value}"
+ end
+ end
+
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
+ buffer = ''
+ params.each do |parent, value|
+ encoded_parent = escape(parent)
+ buffer << "#{to_query.call(encoded_parent, value)}&"
+ end
+ return buffer.chop
+ end
+
+ def self.decode(query)
+ return nil if query == nil
+ # Recursive helper lambda
+ dehash = lambda do |hash|
+ hash.each do |(key, value)|
+ if value.kind_of?(Hash)
+ hash[key] = dehash.call(value)
+ end
+ end
+ # Numeric keys implies an array
+ if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
+ hash.sort.inject([]) do |accu, (_, value)|
+ accu << value; accu
+ end
+ else
+ hash
+ end
+ end
+
+ empty_accumulator = {}
+ return ((query.split('&').map do |pair|
+ pair.split('=', 2) if pair && !pair.empty?
+ end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
+ key = unescape(key)
+ if value.kind_of?(String)
+ value = unescape(value.gsub(/\+/, ' '))
+ end
+
+ array_notation = !!(key =~ /\[\]$/)
+ subkeys = key.split(/[\[\]]+/)
+ current_hash = accu
+ for i in 0...(subkeys.size - 1)
+ subkey = subkeys[i]
+ current_hash[subkey] = {} unless current_hash[subkey]
+ current_hash = current_hash[subkey]
+ end
+ if array_notation
+ current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
+ current_hash[subkeys.last] << value
+ else
+ current_hash[subkeys.last] = value
+ end
+ accu
+ end).inject(empty_accumulator.dup) do |accu, (key, value)|
+ accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
+ accu
+ end
+ end
+ end
+
+ module FlatParamsEncoder
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
+
+ def self.escape(s)
+ return s.to_s.gsub(ESCAPE_RE) {
+ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
+ }.tr(' ', '+')
+ end
+
+ def self.unescape(s)
+ CGI.unescape(s.to_s)
+ end
+
+ def self.encode(params)
+ return nil if params == nil
+
+ if !params.is_a?(Array)
+ if !params.respond_to?(:to_hash)
+ raise TypeError,
+ "Can't convert #{params.class} into Hash."
+ end
+ params = params.to_hash
+ params = params.map do |key, value|
+ key = key.to_s if key.kind_of?(Symbol)
+ [key, value]
+ end
+ # Useful default for OAuth and caching.
+ # Only to be used for non-Array inputs. Arrays should preserve order.
+ params.sort!
+ end
+
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
+ buffer = ''
+ params.each do |key, value|
+ encoded_key = escape(key)
+ value = value.to_s if value == true || value == false
+ if value == nil
+ buffer << "#{encoded_key}&"
+ elsif value.kind_of?(Array)
+ value.each do |sub_value|
+ encoded_value = escape(sub_value)
+ buffer << "#{encoded_key}=#{encoded_value}&"
+ end
+ else
+ encoded_value = escape(value)
+ buffer << "#{encoded_key}=#{encoded_value}&"
+ end
+ end
+ return buffer.chop
+ end
+
+ def self.decode(query)
+ empty_accumulator = {}
+ return nil if query == nil
+ split_query = (query.split('&').map do |pair|
+ pair.split('=', 2) if pair && !pair.empty?
+ end).compact
+ return split_query.inject(empty_accumulator.dup) do |accu, pair|
+ pair[0] = unescape(pair[0])
+ pair[1] = true if pair[1].nil?
+ if pair[1].respond_to?(:to_str)
+ pair[1] = unescape(pair[1].to_str.gsub(/\+/, " "))
+ end
+ if accu[pair[0]].kind_of?(Array)
+ accu[pair[0]] << pair[1]
+ elsif accu[pair[0]]
+ accu[pair[0]] = [accu[pair[0]], pair[1]]
+ else
+ accu[pair[0]] = pair[1]
+ end
+ accu
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # A Builder that processes requests into responses by passing through an inner
+ # middleware stack (heavily inspired by Rack).
+ #
+ # Faraday::Connection.new(:url => 'http://sushi.com') do |builder|
+ # builder.request :url_encoded # Faraday::Request::UrlEncoded
+ # builder.adapter :net_http # Faraday::Adapter::NetHttp
+ # end
+ class RackBuilder
+ attr_accessor :handlers
+
+ # Error raised when trying to modify the stack after calling `lock!`
+ class StackLocked < RuntimeError; end
+
+ # borrowed from ActiveSupport::Dependencies::Reference &
+ # ActionDispatch::MiddlewareStack::Middleware
+ class Handler
+ @@constants_mutex = Mutex.new
+ @@constants = Hash.new { |h, k|
+ value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
+ @@constants_mutex.synchronize { h[k] = value }
+ }
+
+ attr_reader :name
+
+ def initialize(klass, *args, &block)
+ @name = klass.to_s
+ if klass.respond_to?(:name)
+ @@constants_mutex.synchronize { @@constants[@name] = klass }
+ end
+ @args, @block = args, block
+ end
+
+ def klass() @@constants[@name] end
+ def inspect() @name end
+
+ def ==(other)
+ if other.is_a? Handler
+ self.name == other.name
+ elsif other.respond_to? :name
+ klass == other
+ else
+ @name == other.to_s
+ end
+ end
+
+ def build(app)
+ klass.new(app, *@args, &@block)
+ end
+ end
+
+ def initialize(handlers = [])
+ @handlers = handlers
+ if block_given?
+ build(&Proc.new)
+ elsif @handlers.empty?
+ # default stack, if nothing else is configured
+ self.request :url_encoded
+ self.adapter Faraday.default_adapter
+ end
+ end
+
+ def build(options = {})
+ raise_if_locked
+ @handlers.clear unless options[:keep]
+ yield(self) if block_given?
+ end
+
+ def [](idx)
+ @handlers[idx]
+ end
+
+ # Locks the middleware stack to ensure no further modifications are possible.
+ def lock!
+ @handlers.freeze
+ end
+
+ def locked?
+ @handlers.frozen?
+ end
+
+ def use(klass, *args, &block)
+ if klass.is_a? Symbol
+ use_symbol(Faraday::Middleware, klass, *args, &block)
+ else
+ raise_if_locked
+ @handlers << self.class::Handler.new(klass, *args, &block)
+ end
+ end
+
+ def request(key, *args, &block)
+ use_symbol(Faraday::Request, key, *args, &block)
+ end
+
+ def response(key, *args, &block)
+ use_symbol(Faraday::Response, key, *args, &block)
+ end
+
+ def adapter(key, *args, &block)
+ use_symbol(Faraday::Adapter, key, *args, &block)
+ end
+
+ ## methods to push onto the various positions in the stack:
+
+ def insert(index, *args, &block)
+ raise_if_locked
+ index = assert_index(index)
+ handler = self.class::Handler.new(*args, &block)
+ @handlers.insert(index, handler)
+ end
+
+ alias_method :insert_before, :insert
+
+ def insert_after(index, *args, &block)
+ index = assert_index(index)
+ insert(index + 1, *args, &block)
+ end
+
+ def swap(index, *args, &block)
+ raise_if_locked
+ index = assert_index(index)
+ @handlers.delete_at(index)
+ insert(index, *args, &block)
+ end
+
+ def delete(handler)
+ raise_if_locked
+ @handlers.delete(handler)
+ end
+
+ # Processes a Request into a Response by passing it through this Builder's
+ # middleware stack.
+ #
+ # connection - Faraday::Connection
+ # request - Faraday::Request
+ #
+ # Returns a Faraday::Response.
+ def build_response(connection, request)
+ app.call(build_env(connection, request))
+ end
+
+ # The "rack app" wrapped in middleware. All requests are sent here.
+ #
+ # The builder is responsible for creating the app object. After this,
+ # the builder gets locked to ensure no further modifications are made
+ # to the middleware stack.
+ #
+ # Returns an object that responds to `call` and returns a Response.
+ def app
+ @app ||= begin
+ lock!
+ to_app(lambda { |env|
+ response = Response.new
+ response.finish(env) unless env.parallel?
+ env.response = response
+ })
+ end
+ end
+
+ def to_app(inner_app)
+ # last added handler is the deepest and thus closest to the inner app
+ @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
+ end
+
+ def ==(other)
+ other.is_a?(self.class) && @handlers == other.handlers
+ end
+
+ def dup
+ self.class.new(@handlers.dup)
+ end
+
+ # ENV Keys
+ # :method - a symbolized request method (:get, :post)
+ # :body - the request body that will eventually be converted to a string.
+ # :url - URI instance for the current request.
+ # :status - HTTP response status code
+ # :request_headers - hash of HTTP Headers to be sent to the server
+ # :response_headers - Hash of HTTP headers from the server
+ # :parallel_manager - sent if the connection is in parallel mode
+ # :request - Hash of options for configuring the request.
+ # :timeout - open/read timeout Integer in seconds
+ # :open_timeout - read timeout Integer in seconds
+ # :proxy - Hash of proxy options
+ # :uri - Proxy Server URI
+ # :user - Proxy server username
+ # :password - Proxy server password
+ # :ssl - Hash of options for configuring SSL requests.
+ def build_env(connection, request)
+ Env.new(request.method, request.body,
+ connection.build_exclusive_url(request.path, request.params),
+ request.options, request.headers, connection.ssl,
+ connection.parallel_manager)
+ end
+
+ private
+
+ def raise_if_locked
+ raise StackLocked, "can't modify middleware stack after making a request" if locked?
+ end
+
+ def use_symbol(mod, key, *args, &block)
+ use(mod.lookup_middleware(key), *args, &block)
+ end
+
+ def assert_index(index)
+ idx = index.is_a?(Integer) ? index : @handlers.index(index)
+ raise "No such handler: #{index.inspect}" unless idx
+ idx
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Used to setup urls, params, headers, and the request body in a sane manner.
+ #
+ # @connection.post do |req|
+ # req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
+ # req.headers['b'] = '2' # Header
+ # req.params['c'] = '3' # GET Param
+ # req['b'] = '2' # also Header
+ # req.body = 'abc'
+ # end
+ #
+ class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
+ extend MiddlewareRegistry
+
+ register_middleware File.expand_path('../request', __FILE__),
+ :url_encoded => [:UrlEncoded, 'url_encoded'],
+ :multipart => [:Multipart, 'multipart'],
+ :retry => [:Retry, 'retry'],
+ :authorization => [:Authorization, 'authorization'],
+ :basic_auth => [:BasicAuthentication, 'basic_authentication'],
+ :token_auth => [:TokenAuthentication, 'token_authentication'],
+ :instrumentation => [:Instrumentation, 'instrumentation']
+
+ def self.create(request_method)
+ new(request_method).tap do |request|
+ yield(request) if block_given?
+ end
+ end
+
+ # Public: Replace params, preserving the existing hash type
+ def params=(hash)
+ if params
+ params.replace hash
+ else
+ super
+ end
+ end
+
+ # Public: Replace request headers, preserving the existing hash type
+ def headers=(hash)
+ if headers
+ headers.replace hash
+ else
+ super
+ end
+ end
+
+ def url(path, params = nil)
+ if path.respond_to? :query
+ if query = path.query
+ path = path.dup
+ path.query = nil
+ end
+ else
+ path, query = path.split('?', 2)
+ end
+ self.path = path
+ self.params.merge_query query, options.params_encoder
+ self.params.update(params) if params
+ end
+
+ def [](key)
+ headers[key]
+ end
+
+ def []=(key, value)
+ headers[key] = value
+ end
+
+ # ENV Keys
+ # :method - a symbolized request method (:get, :post)
+ # :body - the request body that will eventually be converted to a string.
+ # :url - URI instance for the current request.
+ # :status - HTTP response status code
+ # :request_headers - hash of HTTP Headers to be sent to the server
+ # :response_headers - Hash of HTTP headers from the server
+ # :parallel_manager - sent if the connection is in parallel mode
+ # :request - Hash of options for configuring the request.
+ # :timeout - open/read timeout Integer in seconds
+ # :open_timeout - read timeout Integer in seconds
+ # :proxy - Hash of proxy options
+ # :uri - Proxy Server URI
+ # :user - Proxy server username
+ # :password - Proxy server password
+ # :ssl - Hash of options for configuring SSL requests.
+ def to_env(connection)
+ Env.new(method, body, connection.build_exclusive_url(path, params),
+ options, headers, connection.ssl, connection.parallel_manager)
+ end
+ end
+end
+
--- /dev/null
+module Faraday
+ class Request::Authorization < Faraday::Middleware
+ KEY = "Authorization".freeze unless defined? KEY
+
+ # Public
+ def self.header(type, token)
+ case token
+ when String, Symbol
+ "#{type} #{token}"
+ when Hash
+ build_hash(type.to_s, token)
+ else
+ raise ArgumentError, "Can't build an Authorization #{type} header from #{token.inspect}"
+ end
+ end
+
+ # Internal
+ def self.build_hash(type, hash)
+ offset = KEY.size + type.size + 3
+ comma = ",\n#{' ' * offset}"
+ values = []
+ hash.each do |key, value|
+ values << "#{key}=#{value.to_s.inspect}"
+ end
+ "#{type} #{values * comma}"
+ end
+
+ def initialize(app, type, token)
+ @header_value = self.class.header(type, token)
+ super(app)
+ end
+
+ # Public
+ def call(env)
+ unless env.request_headers[KEY]
+ env.request_headers[KEY] = @header_value
+ end
+ @app.call(env)
+ end
+ end
+end
+
--- /dev/null
+require 'base64'
+
+module Faraday
+ class Request::BasicAuthentication < Request.load_middleware(:authorization)
+ # Public
+ def self.header(login, pass)
+ value = Base64.encode64([login, pass].join(':'))
+ value.gsub!("\n", '')
+ super(:Basic, value)
+ end
+ end
+end
+
--- /dev/null
+module Faraday
+ class Request::Instrumentation < Faraday::Middleware
+ class Options < Faraday::Options.new(:name, :instrumenter)
+ def name
+ self[:name] ||= 'request.faraday'
+ end
+
+ def instrumenter
+ self[:instrumenter] ||= ActiveSupport::Notifications
+ end
+ end
+
+ # Public: Instruments requests using Active Support.
+ #
+ # Measures time spent only for synchronous requests.
+ #
+ # Examples
+ #
+ # ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env|
+ # url = env[:url]
+ # http_method = env[:method].to_s.upcase
+ # duration = ends - starts
+ # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
+ # end
+ def initialize(app, options = nil)
+ super(app)
+ @name, @instrumenter = Options.from(options).values_at(:name, :instrumenter)
+ end
+
+ def call(env)
+ @instrumenter.instrument(@name, env) do
+ @app.call(env)
+ end
+ end
+ end
+end
--- /dev/null
+require File.expand_path("../url_encoded", __FILE__)
+
+module Faraday
+ class Request::Multipart < Request::UrlEncoded
+ self.mime_type = 'multipart/form-data'.freeze
+ DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze unless defined? DEFAULT_BOUNDARY
+
+ def call(env)
+ match_content_type(env) do |params|
+ env.request.boundary ||= DEFAULT_BOUNDARY
+ env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}"
+ env.body = create_multipart(env, params)
+ end
+ @app.call env
+ end
+
+ def process_request?(env)
+ type = request_type(env)
+ env.body.respond_to?(:each_key) and !env.body.empty? and (
+ (type.empty? and has_multipart?(env.body)) or
+ type == self.class.mime_type
+ )
+ end
+
+ def has_multipart?(obj)
+ # string is an enum in 1.8, returning list of itself
+ if obj.respond_to?(:each) && !obj.is_a?(String)
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
+ return true if (val.respond_to?(:content_type) || has_multipart?(val))
+ end
+ end
+ false
+ end
+
+ def create_multipart(env, params)
+ boundary = env.request.boundary
+ parts = process_params(params) do |key, value|
+ Faraday::Parts::Part.new(boundary, key, value)
+ end
+ parts << Faraday::Parts::EpiloguePart.new(boundary)
+
+ body = Faraday::CompositeReadIO.new(parts)
+ env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
+ return body
+ end
+
+ def process_params(params, prefix = nil, pieces = nil, &block)
+ params.inject(pieces || []) do |all, (key, value)|
+ key = "#{prefix}[#{key}]" if prefix
+
+ case value
+ when Array
+ values = value.inject([]) { |a,v| a << [nil, v] }
+ process_params(values, key, all, &block)
+ when Hash
+ process_params(value, key, all, &block)
+ else
+ all << block.call(key, value)
+ end
+ end
+ end
+ end
+end
--- /dev/null
+module Faraday
+ # Catches exceptions and retries each request a limited number of times.
+ #
+ # By default, it retries 2 times and handles only timeout exceptions. It can
+ # be configured with an arbitrary number of retries, a list of exceptions to
+ # handle, a retry interval, a percentage of randomness to add to the retry
+ # interval, and a backoff factor.
+ #
+ # Examples
+ #
+ # Faraday.new do |conn|
+ # conn.request :retry, max: 2, interval: 0.05,
+ # interval_randomness: 0.5, backoff_factor: 2
+ # exceptions: [CustomException, 'Timeout::Error']
+ # conn.adapter ...
+ # end
+ #
+ # This example will result in a first interval that is random between 0.05 and 0.075 and a second
+ # interval that is random between 0.1 and 0.15
+ #
+ class Request::Retry < Faraday::Middleware
+
+ IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
+
+ class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor, :exceptions, :retry_if)
+ DEFAULT_CHECK = lambda { |env,exception| false }
+
+ def self.from(value)
+ if Fixnum === value
+ new(value)
+ else
+ super(value)
+ end
+ end
+
+ def max
+ (self[:max] ||= 2).to_i
+ end
+
+ def interval
+ (self[:interval] ||= 0).to_f
+ end
+
+ def interval_randomness
+ (self[:interval_randomness] ||= 0).to_i
+ end
+
+ def backoff_factor
+ (self[:backoff_factor] ||= 1).to_f
+ end
+
+ def exceptions
+ Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
+ Error::TimeoutError])
+ end
+
+ def retry_if
+ self[:retry_if] ||= DEFAULT_CHECK
+ end
+
+ end
+
+ # Public: Initialize middleware
+ #
+ # Options:
+ # max - Maximum number of retries (default: 2)
+ # interval - Pause in seconds between retries (default: 0)
+ # interval_randomness - The maximum random interval amount expressed
+ # as a float between 0 and 1 to use in addition to the
+ # interval. (default: 0)
+ # backoff_factor - The amount to multiple each successive retry's
+ # interval amount by in order to provide backoff
+ # (default: 1)
+ # exceptions - The list of exceptions to handle. Exceptions can be
+ # given as Class, Module, or String. (default:
+ # [Errno::ETIMEDOUT, Timeout::Error,
+ # Error::TimeoutError])
+ # retry_if - block that will receive the env object and the exception raised
+ # and should decide if the code should retry still the action or
+ # not independent of the retry count. This would be useful
+ # if the exception produced is non-recoverable or if the
+ # the HTTP method called is not idempotent.
+ # (defaults to return false)
+ def initialize(app, options = nil)
+ super(app)
+ @options = Options.from(options)
+ @errmatch = build_exception_matcher(@options.exceptions)
+ end
+
+ def sleep_amount(retries)
+ retry_index = @options.max - retries
+ current_interval = @options.interval * (@options.backoff_factor ** retry_index)
+ random_interval = rand * @options.interval_randomness.to_f * @options.interval
+ current_interval + random_interval
+ end
+
+ def call(env)
+ retries = @options.max
+ request_body = env[:body]
+ begin
+ env[:body] = request_body # after failure env[:body] is set to the response body
+ @app.call(env)
+ rescue @errmatch => exception
+ if retries > 0 && retry_request?(env, exception)
+ retries -= 1
+ sleep sleep_amount(retries + 1)
+ retry
+ end
+ raise
+ end
+ end
+
+ # Private: construct an exception matcher object.
+ #
+ # An exception matcher for the rescue clause can usually be any object that
+ # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
+ def build_exception_matcher(exceptions)
+ matcher = Module.new
+ (class << matcher; self; end).class_eval do
+ define_method(:===) do |error|
+ exceptions.any? do |ex|
+ if ex.is_a? Module
+ error.is_a? ex
+ else
+ error.class.to_s == ex.to_s
+ end
+ end
+ end
+ end
+ matcher
+ end
+
+ private
+
+ def retry_request?(env, exception)
+ IDEMPOTENT_METHODS.include?(env[:method]) || @options.retry_if.call(env, exception)
+ end
+
+ end
+end
--- /dev/null
+module Faraday
+ class Request::TokenAuthentication < Request.load_middleware(:authorization)
+ # Public
+ def self.header(token, options = nil)
+ options ||= {}
+ options[:token] = token
+ super(:Token, options)
+ end
+
+ def initialize(app, token, options = nil)
+ super(app, token, options)
+ end
+ end
+end
+
--- /dev/null
+module Faraday
+ class Request::UrlEncoded < Faraday::Middleware
+ CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
+
+ class << self
+ attr_accessor :mime_type
+ end
+ self.mime_type = 'application/x-www-form-urlencoded'.freeze
+
+ def call(env)
+ match_content_type(env) do |data|
+ params = Faraday::Utils::ParamsHash[data]
+ env.body = params.to_query(env.params_encoder)
+ end
+ @app.call env
+ end
+
+ def match_content_type(env)
+ if process_request?(env)
+ env.request_headers[CONTENT_TYPE] ||= self.class.mime_type
+ yield(env.body) unless env.body.respond_to?(:to_str)
+ end
+ end
+
+ def process_request?(env)
+ type = request_type(env)
+ env.body and (type.empty? or type == self.class.mime_type)
+ end
+
+ def request_type(env)
+ type = env.request_headers[CONTENT_TYPE].to_s
+ type = type.split(';', 2).first if type.index(';')
+ type
+ end
+ end
+end
--- /dev/null
+require 'forwardable'
+
+module Faraday
+ class Response
+ # Used for simple response middleware.
+ class Middleware < Faraday::Middleware
+ def call(env)
+ @app.call(env).on_complete do |environment|
+ on_complete(environment)
+ end
+ end
+
+ # Override this to modify the environment after the response has finished.
+ # Calls the `parse` method if defined
+ def on_complete(env)
+ env.body = parse(env.body) if respond_to?(:parse) && env.parse_body?
+ end
+ end
+
+ extend Forwardable
+ extend MiddlewareRegistry
+
+ register_middleware File.expand_path('../response', __FILE__),
+ :raise_error => [:RaiseError, 'raise_error'],
+ :logger => [:Logger, 'logger']
+
+ def initialize(env = nil)
+ @env = Env.from(env) if env
+ @on_complete_callbacks = []
+ end
+
+ attr_reader :env
+
+ def_delegators :env, :to_hash
+
+ def status
+ finished? ? env.status : nil
+ end
+
+ def headers
+ finished? ? env.response_headers : {}
+ end
+ def_delegator :headers, :[]
+
+ def body
+ finished? ? env.body : nil
+ end
+
+ def finished?
+ !!env
+ end
+
+ def on_complete
+ if not finished?
+ @on_complete_callbacks << Proc.new
+ else
+ yield(env)
+ end
+ return self
+ end
+
+ def finish(env)
+ raise "response already finished" if finished?
+ @on_complete_callbacks.each { |callback| callback.call(env) }
+ @env = Env.from(env)
+ return self
+ end
+
+ def success?
+ finished? && env.success?
+ end
+
+ # because @on_complete_callbacks cannot be marshalled
+ def marshal_dump
+ !finished? ? nil : {
+ :status => @env.status, :body => @env.body,
+ :response_headers => @env.response_headers
+ }
+ end
+
+ def marshal_load(env)
+ @env = Env.from(env)
+ end
+
+ # Expand the env with more properties, without overriding existing ones.
+ # Useful for applying request params after restoring a marshalled Response.
+ def apply_request(request_env)
+ raise "response didn't finish yet" unless finished?
+ @env = Env.from(request_env).update(@env)
+ return self
+ end
+ end
+end
--- /dev/null
+require 'forwardable'
+
+module Faraday
+ class Response::Logger < Response::Middleware
+ extend Forwardable
+
+ def initialize(app, logger = nil)
+ super(app)
+ @logger = logger || begin
+ require 'logger'
+ ::Logger.new(STDOUT)
+ end
+ end
+
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
+
+ def call(env)
+ info "#{env.method} #{env.url.to_s}"
+ debug('request') { dump_headers env.request_headers }
+ super
+ end
+
+ def on_complete(env)
+ info('Status') { env.status.to_s }
+ debug('response') { dump_headers env.response_headers }
+ end
+
+ private
+
+ def dump_headers(headers)
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
+ end
+ end
+end
--- /dev/null
+module Faraday
+ class Response::RaiseError < Response::Middleware
+ ClientErrorStatuses = 400...600
+
+ def on_complete(env)
+ case env[:status]
+ when 404
+ raise Faraday::Error::ResourceNotFound, response_values(env)
+ when 407
+ # mimic the behavior that we get with proxy requests with HTTPS
+ raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
+ when ClientErrorStatuses
+ raise Faraday::Error::ClientError, response_values(env)
+ end
+ end
+
+ def response_values(env)
+ {:status => env.status, :headers => env.response_headers, :body => env.body}
+ end
+ end
+end
--- /dev/null
+begin
+ require 'composite_io'
+ require 'parts'
+ require 'stringio'
+rescue LoadError
+ $stderr.puts "Install the multipart-post gem."
+ raise
+end
+
+module Faraday
+ # Similar but not compatible with ::CompositeReadIO provided by multipart-post.
+ class CompositeReadIO
+ def initialize(*parts)
+ @parts = parts.flatten
+ @ios = @parts.map { |part| part.to_io }
+ @index = 0
+ end
+
+ def length
+ @parts.inject(0) { |sum, part| sum + part.length }
+ end
+
+ def rewind
+ @ios.each { |io| io.rewind }
+ @index = 0
+ end
+
+ # Read from IOs in order until `length` bytes have been received.
+ def read(length = nil, outbuf = nil)
+ got_result = false
+ outbuf = outbuf ? outbuf.replace("") : ""
+
+ while io = current_io
+ if result = io.read(length)
+ got_result ||= !result.nil?
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
+ outbuf << result
+ length -= result.length if length
+ break if length == 0
+ end
+ advance_io
+ end
+ (!got_result && length) ? nil : outbuf
+ end
+
+ def close
+ @ios.each { |io| io.close }
+ end
+
+ def ensure_open_and_readable
+ # Rubinius compatibility
+ end
+
+ private
+
+ def current_io
+ @ios[@index]
+ end
+
+ def advance_io
+ @index += 1
+ end
+ end
+
+ UploadIO = ::UploadIO
+ Parts = ::Parts
+end
--- /dev/null
+require 'thread'
+Faraday.require_libs 'parameters'
+
+module Faraday
+ module Utils
+ extend self
+
+ # Adapted from Rack::Utils::HeaderHash
+ class Headers < ::Hash
+ def self.from(value)
+ new(value)
+ end
+
+ def initialize(hash = nil)
+ super()
+ @names = {}
+ self.update(hash || {})
+ end
+
+ # need to synchronize concurrent writes to the shared KeyMap
+ keymap_mutex = Mutex.new
+
+ # symbol -> string mapper + cache
+ KeyMap = Hash.new do |map, key|
+ value = if key.respond_to?(:to_str)
+ key
+ else
+ key.to_s.split('_'). # :user_agent => %w(user agent)
+ each { |w| w.capitalize! }. # => %w(User Agent)
+ join('-') # => "User-Agent"
+ end
+ keymap_mutex.synchronize { map[key] = value }
+ end
+ KeyMap[:etag] = "ETag"
+
+ def [](k)
+ k = KeyMap[k]
+ super(k) || super(@names[k.downcase])
+ end
+
+ def []=(k, v)
+ k = KeyMap[k]
+ k = (@names[k.downcase] ||= k)
+ # join multiple values with a comma
+ v = v.to_ary.join(', ') if v.respond_to? :to_ary
+ super(k, v)
+ end
+
+ def fetch(k, *args, &block)
+ k = KeyMap[k]
+ key = @names.fetch(k.downcase, k)
+ super(key, *args, &block)
+ end
+
+ def delete(k)
+ k = KeyMap[k]
+ if k = @names[k.downcase]
+ @names.delete k.downcase
+ super(k)
+ end
+ end
+
+ def include?(k)
+ @names.include? k.downcase
+ end
+
+ alias_method :has_key?, :include?
+ alias_method :member?, :include?
+ alias_method :key?, :include?
+
+ def merge!(other)
+ other.each { |k, v| self[k] = v }
+ self
+ end
+ alias_method :update, :merge!
+
+ def merge(other)
+ hash = dup
+ hash.merge! other
+ end
+
+ def replace(other)
+ clear
+ self.update other
+ self
+ end
+
+ def to_hash() ::Hash.new.update(self) end
+
+ def parse(header_string)
+ return unless header_string && !header_string.empty?
+ header_string.split(/\r\n/).
+ tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
+ map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
+ each { |key, value|
+ # join multiple values with a comma
+ if self[key]
+ self[key] << ', ' << value
+ else
+ self[key] = value
+ end
+ }
+ end
+ end
+
+ # hash with stringified keys
+ class ParamsHash < Hash
+ def [](key)
+ super(convert_key(key))
+ end
+
+ def []=(key, value)
+ super(convert_key(key), value)
+ end
+
+ def delete(key)
+ super(convert_key(key))
+ end
+
+ def include?(key)
+ super(convert_key(key))
+ end
+
+ alias_method :has_key?, :include?
+ alias_method :member?, :include?
+ alias_method :key?, :include?
+
+ def update(params)
+ params.each do |key, value|
+ self[key] = value
+ end
+ self
+ end
+ alias_method :merge!, :update
+
+ def merge(params)
+ dup.update(params)
+ end
+
+ def replace(other)
+ clear
+ update(other)
+ end
+
+ def merge_query(query, encoder = nil)
+ if query && !query.empty?
+ update((encoder || Utils.default_params_encoder).decode(query))
+ end
+ self
+ end
+
+ def to_query(encoder = nil)
+ (encoder || Utils.default_params_encoder).encode(self)
+ end
+
+ private
+
+ def convert_key(key)
+ key.to_s
+ end
+ end
+
+ def build_query(params)
+ FlatParamsEncoder.encode(params)
+ end
+
+ def build_nested_query(params)
+ NestedParamsEncoder.encode(params)
+ end
+
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
+
+ def escape(s)
+ s.to_s.gsub(ESCAPE_RE) {|match|
+ '%' + match.unpack('H2' * match.bytesize).join('%').upcase
+ }.tr(' ', '+')
+ end
+
+ def unescape(s) CGI.unescape s.to_s end
+
+ DEFAULT_SEP = /[&;] */n
+
+ # Adapted from Rack
+ def parse_query(query)
+ FlatParamsEncoder.decode(query)
+ end
+
+ def parse_nested_query(query)
+ NestedParamsEncoder.decode(query)
+ end
+
+ def default_params_encoder
+ @default_params_encoder ||= NestedParamsEncoder
+ end
+
+ class << self
+ attr_writer :default_params_encoder
+ end
+
+ # Stolen from Rack
+ def normalize_params(params, name, v = nil)
+ name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
+ k = $1 || ''
+ after = $' || ''
+
+ return if k.empty?
+
+ if after == ""
+ if params[k]
+ params[k] = Array[params[k]] unless params[k].kind_of?(Array)
+ params[k] << v
+ else
+ params[k] = v
+ end
+ elsif after == "[]"
+ params[k] ||= []
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
+ params[k] << v
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
+ child_key = $1
+ params[k] ||= []
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
+ if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
+ normalize_params(params[k].last, child_key, v)
+ else
+ params[k] << normalize_params({}, child_key, v)
+ end
+ else
+ params[k] ||= {}
+ raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
+ params[k] = normalize_params(params[k], after, v)
+ end
+
+ return params
+ end
+
+ # Normalize URI() behavior across Ruby versions
+ #
+ # url - A String or URI.
+ #
+ # Returns a parsed URI.
+ def URI(url)
+ if url.respond_to?(:host)
+ url
+ elsif url.respond_to?(:to_str)
+ default_uri_parser.call(url)
+ else
+ raise ArgumentError, "bad argument (expected URI object or URI string)"
+ end
+ end
+
+ def default_uri_parser
+ @default_uri_parser ||= begin
+ require 'uri'
+ Kernel.method(:URI)
+ end
+ end
+
+ def default_uri_parser=(parser)
+ @default_uri_parser = if parser.respond_to?(:call) || parser.nil?
+ parser
+ else
+ parser.method(:parse)
+ end
+ end
+
+ # Receives a String or URI and returns just the path with the query string sorted.
+ def normalize_path(url)
+ url = URI(url)
+ (url.path.start_with?('/') ? url.path : '/' + url.path) +
+ (url.query ? "?#{sort_query_params(url.query)}" : "")
+ end
+
+ # Recursive hash update
+ def deep_merge!(target, hash)
+ hash.each do |key, value|
+ if Hash === value and Hash === target[key]
+ target[key] = deep_merge(target[key], value)
+ else
+ target[key] = value
+ end
+ end
+ target
+ end
+
+ # Recursive hash merge
+ def deep_merge(source, hash)
+ deep_merge!(source.dup, hash)
+ end
+
+ protected
+
+ def sort_query_params(query)
+ query.split('&').sort.join('&')
+ end
+ end
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2013 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+module MultipartPost
+ VERSION = "2.0.0"
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2013 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+require 'parts'
+ module Multipartable
+ DEFAULT_BOUNDARY = "-----------RubyMultipartPost"
+ def initialize(path, params, headers={}, boundary = DEFAULT_BOUNDARY)
+ headers = headers.clone # don't want to modify the original variable
+ parts_headers = headers.delete(:parts) || {}
+ super(path, headers)
+ parts = params.map do |k,v|
+ case v
+ when Array
+ v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
+ else
+ Parts::Part.new(boundary, k, v, parts_headers[k])
+ end
+ end.flatten
+ parts << Parts::EpiloguePart.new(boundary)
+ ios = parts.map {|p| p.to_io }
+ self.set_content_type(headers["Content-Type"] || "multipart/form-data",
+ { "boundary" => boundary })
+ self.content_length = parts.inject(0) {|sum,i| sum + i.length }
+ self.body_stream = CompositeReadIO.new(*ios)
+ end
+ end
--- /dev/null
+#--
+# Copyright (c) 2007-2012 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+require 'net/http'
+require 'stringio'
+require 'cgi'
+require 'composite_io'
+require 'multipartable'
+require 'parts'
+
+module Net #:nodoc:
+ class HTTP #:nodoc:
+ class Put
+ class Multipart < Put
+ include Multipartable
+ end
+ end
+ class Post #:nodoc:
+ class Multipart < Post
+ include Multipartable
+ end
+ end
+ end
+end
--- /dev/null
+#--
+# Copyright (c) 2007-2013 Nick Sieger.
+# See the file README.txt included with the distribution for
+# software license details.
+#++
+
+module Parts
+ module Part #:nodoc:
+ def self.new(boundary, name, value, headers = {})
+ headers ||= {} # avoid nil values
+ if file?(value)
+ FilePart.new(boundary, name, value, headers)
+ else
+ ParamPart.new(boundary, name, value, headers)
+ end
+ end
+
+ def self.file?(value)
+ value.respond_to?(:content_type) && value.respond_to?(:original_filename)
+ end
+
+ def length
+ @part.length
+ end
+
+ def to_io
+ @io
+ end
+ end
+
+ class ParamPart
+ include Part
+ def initialize(boundary, name, value, headers = {})
+ @part = build_part(boundary, name, value, headers)
+ @io = StringIO.new(@part)
+ end
+
+ def length
+ @part.bytesize
+ end
+
+ def build_part(boundary, name, value, headers = {})
+ part = ''
+ part << "--#{boundary}\r\n"
+ part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
+ part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
+ part << "\r\n"
+ part << "#{value}\r\n"
+ end
+ end
+
+ # Represents a part to be filled from file IO.
+ class FilePart
+ include Part
+ attr_reader :length
+ def initialize(boundary, name, io, headers = {})
+ file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
+ @head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
+ io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
+ @foot = "\r\n"
+ @length = @head.bytesize + file_length + @foot.length
+ @io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
+ end
+
+ def build_head(boundary, name, filename, type, content_len, opts = {}, headers = {})
+ trans_encoding = opts["Content-Transfer-Encoding"] || "binary"
+ content_disposition = opts["Content-Disposition"] || "form-data"
+
+ part = ''
+ part << "--#{boundary}\r\n"
+ part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
+ part << "Content-Length: #{content_len}\r\n"
+ if content_id = opts["Content-ID"]
+ part << "Content-ID: #{content_id}\r\n"
+ end
+
+ if headers["Content-Type"] != nil
+ part << "Content-Type: " + headers["Content-Type"] + "\r\n"
+ else
+ part << "Content-Type: #{type}\r\n"
+ end
+
+ part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
+ part << "\r\n"
+ end
+ end
+
+ # Represents the epilogue or closing boundary.
+ class EpiloguePart
+ include Part
+ def initialize(boundary)
+ @part = "--#{boundary}--\r\n\r\n"
+ @io = StringIO.new(@part)
+ end
+ end
+end
--- /dev/null
+{
+ "name": "aimonb-aviator",
+ "version": "0.5.1",
+ "author": "aimonb",
+ "summary": "Puppet feature wrapper for the Aviator OpenStack API library for Ruby",
+ "license": "MIT License",
+ "source": "https://github.com/aimonb/puppet_aviator",
+ "project_page": "https://github.com/aimonb/puppet_aviator",
+ "issues_url": "https://github.com/aimonb/puppet_aviator/issues",
+ "description": "UNKNOWN",
+ "dependencies": [
+
+ ]
+}