1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

Merge changes from upstream fog 1.8.0 and later updates.

This commit is contained in:
Rupak Ganguly 2013-01-18 15:06:41 -05:00
commit 03bf99d9d3
2787 changed files with 88180 additions and 24691 deletions

View file

@ -1,3 +1,3 @@
README.rdoc
README.md
lib/**/*.rb
bin/*

12
.gitignore vendored
View file

@ -2,14 +2,22 @@
*.gem
*.rbc
*.sw?
.rvmrc
.bundle
.DS_Store
.idea
.yardoc
/tests/.fog
bin/*
!bin/fog
.fog
coverage
doc/*
docs/_site/*
docs/about/supported_services.markdown
Gemfile.lock
rdoc
yardoc
pkg
spec/credentials.yml
.idea
vendor/*
tags

82
.irbrc Normal file
View file

@ -0,0 +1,82 @@
## This is primarily used for easier testing and development or
# usage of Fog.
#
# How to use:
# 1. Add this at the end of your `.irbrc` in your home directory.
#
# @working_directory = Dir.pwd
# @local_irbrc = File.join(@working_directory, '.irbrc')
#
# if @working_directory != ENV['HOME']
# load @local_irbrc if File.exists?(@local_irbrc)
# end
#
# remove_instance_variable(:@working_directory)
# remove_instance_variable(:@local_irbrc)
#
# 2. Inside the Fog execute `bundle exec irb`
#
require 'fog'
class ConnectionManager < Hash
def [](key)
$connection_manager_previous_key = key
super(key)
end
def []=(key, value)
$connection_manager_previous_key = key
super(key, value)
end
end
def connections
return @connections if @connections
@connections = ConnectionManager.new
end
def connection
connections[$connection_manager_previous_key]
end
def connect_openstack(username, password, tenant = nil, url = 'http://192.168.27.100:35357/')
parameters = {
:provider => 'openstack',
:openstack_api_key => password,
:openstack_username => username,
:openstack_auth_url => "#{url}v2.0/tokens"
}
parameters.merge!(:openstack_tenant => tenant) if tenant
key = username.to_sym
set_service(key, Fog::Identity, parameters)
set_service(key, Fog::Compute, parameters)
set_service(key, Fog::Volume, parameters)
set_service(key, Fog::Image, parameters)
end
def connect(parameters)
connections_count = connections.count
connections[connections_count] = Hash.new
set_service(connections_count, Fog::Identity, parameters)
set_service(connections_count, Fog::Compute , parameters)
set_service(connections_count, Fog::Storage , parameters)
set_service(connections_count, Fog::Volume , parameters)
set_service(connections_count, Fog::Image , parameters)
set_service(connections_count, Fog::DNS , parameters)
set_service(connections_count, Fog::CDN , parameters)
connection
end
def set_service(connections_count, type, parameters)
service_symbol = type.to_s.split('::').last.downcase.to_sym
connections[connections_count].merge!({
service_symbol => type.new(parameters)
})
rescue
# Service not available
end

21
.travis.yml Normal file
View file

@ -0,0 +1,21 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
script: FOG_MOCK=true bundle exec shindont
notifications:
email: false
irc:
channels:
- "irc.freenode.org#ruby-fog"
template:
- "[#%{build_number}] %{message} %{build_url}"
- "[#%{build_number}] %{commit} on %{branch} by %{author}"
- "[#%{build_number}] %{compare_url}"
on_success: always
on_failure: always
use_notice: false

156
README.md Normal file
View file

@ -0,0 +1,156 @@
![fog](http://geemus.s3.amazonaws.com/fog.png)
fog is the Ruby cloud services library, top to bottom:
* Collections provide a simplified interface, making clouds easier to work with and switch between.
* Requests allow power users to get the most out of the features of each individual cloud.
* Mocks make testing and integrating a breeze.
[![Build Status](https://secure.travis-ci.org/fog/fog.png?branch=master)](http://travis-ci.org/fog/fog)
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/fog/fog)
[![Gem Version](https://fury-badge.herokuapp.com/rb/fog.png)](http://badge.fury.io/rb/fog)
[![Dependency Status](https://gemnasium.com/fog/fog.png)](https://gemnasium.com/fog/fog)
## Getting Started
sudo gem install fog
Now type `fog` to try stuff, confident that fog will let you know what to do.
Here is an example of wading through server creation for Amazon Elastic Compute Cloud:
>> server = Compute[:aws].servers.create
ArgumentError: image_id is required for this operation
>> server = Compute[:aws].servers.create(:image_id => 'ami-5ee70037')
<Fog::AWS::EC2::Server [...]>
>> server.destroy # cleanup after yourself or regret it, trust me
true
## Collections
A high level interface to each cloud is provided through collections, such as `images` and `servers`.
You can see a list of available collections by calling `collections` on the connection object.
You can try it out using the `fog` command:
>> Compute[:aws].collections
[:addresses, :directories, ..., :volumes, :zones]
Some collections are available across multiple providers:
* compute providers have `flavors`, `images` and `servers`
* dns providers have `zones` and `records`
* storage providers have `directories` and `files`
Collections share basic CRUD type operations, such as:
* `all` - fetch every object of that type from the provider.
* `create` - initialize a new record locally and a remote resource with the provider.
* `get` - fetch a single object by it's identity from the provider.
* `new` - initialize a new record locally, but do not create a remote resource with the provider.
As an example, we'll try initializing and persisting a Rackspace Cloud server:
require 'fog'
compute = Fog::Compute.new(
:provider => 'Rackspace',
:rackspace_api_key => key,
:rackspace_username => username
)
# boot a gentoo server (flavor 1 = 256, image 3 = gentoo 2008.0)
server = compute.servers.create(:flavor_id => 1, :image_id => 3, :name => 'my_server')
server.wait_for { ready? } # give server time to boot
# DO STUFF
server.destroy # cleanup after yourself or regret it, trust me
## Models
Many of the collection methods return individual objects, which also provide common methods:
* `destroy` - will destroy the persisted object from the provider
* `save` - persist the object to the provider
* `wait_for` - takes a block and waits for either the block to return true for the object or for a timeout (defaults to 10 minutes)
## Mocks
As you might imagine, testing code using Fog can be slow and expensive, constantly turning on and and shutting down instances.
Mocking allows skipping this overhead by providing an in memory representation resources as you make requests.
Enabling mocking easy to use, before you run other commands, simply run:
Fog.mock!
Then proceed as usual, if you run into unimplemented mocks, fog will raise an error and as always contributions are welcome!
## Requests
Requests allow you to dive deeper when the models just can't cut it.
You can see a list of available requests by calling `#requests` on the connection object.
For instance, ec2 provides methods related to reserved instances that don't have any models (yet). Here is how you can lookup your reserved instances:
$ fog
>> Compute[:aws].describe_reserved_instances
#<Excon::Response [...]>
It will return an [excon](http://github.com/geemus/excon) response, which has `body`, `headers` and `status`. Both return nice hashes.
## Go forth and conquer
Play around and use the console to explore or check out [fog.io](http://fog.io) for more details and examples.
Once you are ready to start scripting fog, here is a quick hint on how to make connections without the command line thing to help you.
# create a compute connection
compute = Fog::Compute.new(:provider => 'AWS', :aws_access_key_id => ACCESS_KEY_ID, :aws_secret_access_key => SECRET_ACCESS_KEY)
# compute operations go here
# create a storage connection
storage = Fog::Storage.new(:provider => 'AWS', :aws_access_key_id => ACCESS_KEY_ID, :aws_secret_access_key => SECRET_ACCESS_KEY)
# storage operations go here
geemus says: "That should give you everything you need to get started, but let me know if there is anything I can do to help!"
## Contributing
* Find something you would like to work on.
* Look for anything you can help with in the [issue tracker](https://github.com/fog/fog/issues).
* Look at the [code quality metrics](https://codeclimate.com/github/fog/fog) for anything you can help clean up.
* Or anything else!
* Fork the project and do your work in a topic branch.
* Make sure your changes will work on both Ruby 1.8.7 and Ruby 1.9
* Add a config at `tests/.fog` for the component you want to test.
* Add shindo tests to prove your code works and run all the tests using `bundle exec rake`.
* Rebase your branch against `fog/fog` to make sure everything is up to date.
* Commit your changes and send a pull request.
## Additional Resources
[fog.io](http://fog.io)
## Copyright
(The MIT License)
Copyright (c) 2013 [geemus (Wesley Beary)](http://github.com/geemus)
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.

View file

@ -1,154 +0,0 @@
http://geemus.s3.amazonaws.com/fog.png
fog is the Ruby cloud computing library, top to bottom:
* Collections provide a simplified interface, making clouds easier to work with and switch between.
* Requests allow power users to get the most out of the features of each individual cloud.
* Mocks make testing and integrating a breeze.
== Getting Started
sudo gem install fog
Now type 'fog' to try stuff, confident that fog will let you know what to do. Here is an example of wading through server creation for Amazon Elastic Compute Cloud:
>> server = Compute[:aws].servers.create
ArgumentError: image_id is required for this operation
>> server = Compute[:aws].servers.create(:image_id => 'ami-5ee70037')
<Fog::AWS::EC2::Server [...]>
>> server.destroy # cleanup after yourself or regret it, trust me
true
== Collections
A high level interface to each cloud is provided through collections, such as `images` and `servers`.
You can see a list of available collections by calling `collections` on the connection object. You can try it out using the `fog` command:
>> Compute[:aws].collections
[:addresses, :directories, ..., :volumes, :zones]
Some collections are available across multiple providers:
* compute providers have +flavors+, +images+ and +servers+
* dns providers have +zones+ and +records+
* storage providers have +directories+ and +files+
Collections share basic CRUD type operations, such as:
* +all+ - fetch every object of that type from the provider.
* +create+ - initialize a new record locally and a remote resource with the provider.
* +get+ - fetch a single object by it's identity from the provider.
* +new+ - initialize a new record locally, but do not create a remote resource with the provider.
As an example, we'll try initializing and persisting a Rackspace Cloud server:
require 'fog'
compute = Fog::Compute.new(
:provider => 'Rackspace',
:rackspace_api_key => key,
:rackspace_username => username
)
# boot a gentoo server (flavor 1 = 256, image 3 = gentoo 2008.0)
server = compute.servers.create(:flavor_id => 1, :image_id => 3, :name => 'my_server')
server.wait_for { ready? } # give server time to boot
# DO STUFF
server.destroy # cleanup after yourself or regret it, trust me
== Models
Many of the collection methods return individual objects, which also provide common methods:
* +destroy+ - will destroy the persisted object from the provider
* +save+ - persist the object to the provider
* +wait_for+ - takes a block and waits for either the block to return true for the object or for a timeout (defaults to 10 minutes)
== Mocks
As you might imagine, testing code using Fog can be slow and expensive, constantly turning on and and shutting down instances.
Mocking allows skipping this overhead by providing an in memory representation resources as you make requests.
Enabling mocking easy to use, before you run other commands, simply run:
Fog.mock!
Then proceed as usual, if you run into unimplemented mocks fog will raise an error and as always contributions are welcome!
== Requests
Requests allow you to dive deeper when the models just can't cut it.
You can see a list of available requests by calling #requests on the connection object.
For instance, ec2 provides methods related to reserved instances that don't have any models (yet). Here is how you can lookup your reserved instances:
$ fog
>> Compute[:aws].describe_reserved_instances
#<Excon::Response [...]>
It will return an {excon}[http://github.com/geemus/excon] response, which has `body`, `headers` and `status`. Both return nice hashes.
== Go forth and conquer
Play around and use the console to explore or check out {fog.io}[http://fog.io] for more details and examples. Once you are ready to start scripting fog, here is a quick hint on how to make connections without the command line thing to help you.
# create a compute connection
compute = Fog::Compute.new(:provider => 'AWS', :aws_access_key_id => ACCESS_KEY_ID, :aws_secret_access_key => SECRET_ACCESS_KEY)
# compute operations go here
# create a storage connection
storage = Fog::Storage.new(:provider => 'AWS', :aws_access_key_id => ACCESS_KEY_ID, :aws_secret_access_key => SECRET_ACCESS_KEY)
# storage operations go here
geemus says: "That should give you everything you need to get started, but let me know if there is anything I can do to help!"
== Contributing
* Find something you would like to work on. For suggestions look for the `easy`, `medium` and `hard` tags in the {issues}[http://github.com/geemus/fog/issues]
* Fork the project and do your work in a topic branch.
* Add shindo tests to prove your code works and run all the tests using `bundle exec rake`.
* Rebase your branch against geemus/fog to make sure everything is up to date.
* Commit your changes and send a pull request.
== T-Shirts
Wonder how you can get a lovely fog shirt? Look no further!
* Blue shirts go to people who have contributed indirectly, great examples are writing blog posts or giving lightning talks.
* Grey shirts and a follow from @fog go to people who have made it on to the {contributors list}[https://github.com/geemus/fog/contributors] by submitting code.
* Black shirts go to people who have made it on to the {collaborators list}[https://github.com/api/v2/json/repos/show/geemus/fog/collaborators] by coercing geemus into adding them.
== Additional Resources
{fog.io}[http://fog.io]
== Sponsorship
http://www.engineyard.com/images/logo.png
All new work on fog is sponsored by {Engine Yard}[http://engineyard.com]
== Copyright
(The MIT License)
Copyright (c) 2010 {geemus (Wesley Beary)}[http://github.com/geemus]
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.

39
RELEASE.md Normal file
View file

@ -0,0 +1,39 @@
# Release process
This is fog's current release process, documented so people know what is
currently done.
## Versioning
fog uses semantic versioning (http://semver.org/)
## When we release
Releases occur monthly and are manually handled by fog's Benevolent
Dictator Wes (@geemus).
To request a new release please raise an issue.
## Prepare the release
* Ensure the code is passing on the CI server [![Build Status](https://secure.travis-ci.org/fog/fog.png?branch=master)](http://travis-ci.org/fog/fog)
* Ensure the code is passing for live tests (Requires Credentials for all
services)
* Ensure working on **master**
* Update the version number (`lib/fog/version.rb`)
* Run `rake changelog` to update `changelog.txt`
* Run `rake release` to prepare the release which does:
* Prepares the release (`rake release:prepare`)
* Builds the gem
* Tags the commit
* Creates commits for version
* Publishes the release (`rake release:publish`)
* Pushes commit and tag to Github (Requires Credentials)
* Pushes gem to Rubygems (Requires Credentials)
## Announce the release
Once the release is prepared and uploaded it needs to be announced.
* Send an email to https://groups.google.com/forum/?fromgroups#!forum/ruby-fog
* Tweet as @fog on Twitter (Requires Credentials)

373
Rakefile
View file

@ -1,6 +1,8 @@
require 'rubygems'
require 'bundler/setup'
require 'date'
require 'rubygems'
require 'rubygems/package_task'
require 'yard'
require File.dirname(__FILE__) + '/lib/fog'
#############################################################################
@ -14,8 +16,7 @@ def name
end
def version
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
Fog::VERSION
end
def date
@ -44,58 +45,20 @@ end
#
#############################################################################
GEM_NAME = "#{name}"
task :default => :test
require "tasks/test_task"
Fog::Rake::TestTask.new
namespace :test do
task :dynect do
[false].each do |mock|
sh("export FOG_MOCK=#{mock} && bundle exec shindont tests/dns/requests/dynect")
#sh("export FOG_MOCK=#{mock} && bundle exec shindont tests/dns/models/")
task :vsphere do
[true].each do |mock|
sh("export FOG_MOCK=#{mock} && bundle exec shindont tests/vsphere")
end
end
end
task :examples do
sh("export FOG_MOCK=false && bundle exec shindont examples")
# some don't provide mocks so we'll leave this out for now
# sh("export FOG_MOCK=true && bundle exec shindont examples")
end
task :test do # => :examples do
Rake::Task[:mock_tests].invoke && Rake::Task[:examples].invoke && Rake::Task[:real_tests].invoke
end
def tests(mocked)
Formatador.display_line
sh("export FOG_MOCK=#{mocked} && bundle exec spec spec")
Formatador.display_line
start = Time.now.to_i
threads = []
Thread.main[:results] = []
Fog.providers.each do |provider|
threads << Thread.new do
Thread.main[:results] << {
:provider => provider,
:success => sh("export FOG_MOCK=#{mocked} && bundle exec shindont +#{provider.downcase}")
}
end
end
threads.each do |thread|
thread.join
end
Formatador.display_table(Thread.main[:results].sort {|x,y| x[:provider] <=> y[:provider]})
Formatador.display_line("[bold]FOG_MOCK=#{mocked}[/] tests completed in [bold]#{Time.now.to_i - start}[/] seconds")
Formatador.display_line
end
task :mock_tests do
tests(true)
end
task :real_tests do
tests(false)
end
task :nuke do
Fog.providers.each do |provider|
next if ['Vmfusion'].include?(provider)
@ -121,22 +84,6 @@ task :nuke do
end
end
desc "Generate RCov test coverage and open in your browser"
task :coverage do
require 'rcov'
sh "rm -fr coverage"
sh "rcov test/test_*.rb"
sh "open coverage/index.html"
end
require 'rdoc/task'
RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "#{name} #{version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
desc "Open an irb session preloaded with this library"
task :console do
sh "irb -rubygems -r ./lib/#{name}.rb"
@ -148,18 +95,44 @@ end
#
#############################################################################
task :release => :build do
unless `git branch` =~ /^\* master$/
puts "You must be on the master branch to release!"
exit!
task :release => ["release:prepare", "release:publish"]
namespace :release do
task :preflight do
unless `git branch` =~ /^\* master$/
puts "You must be on the master branch to release!"
exit!
end
if `git tag` =~ /^\* v#{version}$/
puts "Tag v#{version} already exists!"
exit!
end
end
sh "gem install pkg/#{name}-#{version}.gem"
task :prepare => :preflight do
Rake::Task[:build].invoke
sh "gem install pkg/#{name}-#{version}.gem"
Rake::Task[:git_mark_release].invoke
end
task :publish do
Rake::Task[:git_push_release].invoke
Rake::Task[:gem_push].invoke
end
end
task :git_mark_release do
sh "git commit --allow-empty -a -m 'Release #{version}'"
sh "git tag v#{version}"
end
task :git_push_release do
sh "git push origin master"
sh "git push origin v#{version}"
end
task :gem_push do
sh "gem push pkg/#{name}-#{version}.gem"
Rake::Task[:docs].invoke
end
task :build => :gemspec do
@ -184,7 +157,7 @@ task :gemspec => :validate do
end
task :validate do
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}", "lib/tasks"]
unless libfiles.empty?
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
exit!
@ -195,256 +168,12 @@ task :validate do
end
end
task :changelog do
timestamp = Time.now.utc.strftime('%m/%d/%Y')
sha = `git log | head -1`.split(' ').last
changelog = ["#{version} #{timestamp} #{sha}"]
changelog << ('=' * changelog[0].length)
changelog << ''
require 'multi_json'
github_repo_data = MultiJson.decode(Excon.get('http://github.com/api/v2/json/repos/show/geemus/fog').body)
data = github_repo_data['repository'].reject {|key, value| !['forks', 'open_issues', 'watchers'].include?(key)}
github_collaborator_data = MultiJson.decode(Excon.get('http://github.com/api/v2/json/repos/show/geemus/fog/collaborators').body)
data['collaborators'] = github_collaborator_data['collaborators'].length
rubygems_data = MultiJson.decode(Excon.get('https://rubygems.org/api/v1/gems/fog.json').body)
data['downloads'] = rubygems_data['downloads']
stats = []
for key in data.keys.sort
stats << "'#{key}' => #{data[key]}"
end
changelog << "Stats! { #{stats.join(', ')} }"
changelog << ''
last_sha = `cat changelog.txt | head -1`.split(' ').last
shortlog = `git shortlog #{last_sha}..HEAD`
changes = {}
committers = {}
for line in shortlog.split("\n")
if line =~ /^\S/
committer = line.split(' (', 2).first
committers[committer] = 0
elsif line =~ /^\s*((Merge.*)|(Release.*))?$/
# skip empty lines, Merge and Release commits
else
unless line[-1..-1] == '.'
line << '.'
end
line.lstrip!
line.gsub!(/^\[([^\]]*)\] /, '')
tag = $1 || 'misc'
changes[tag] ||= []
changes[tag] << (line << ' thanks ' << committer)
committers[committer] += 1
end
end
for committer, commits in committers.to_a.sort {|x,y| y[1] <=> x[1]}
if [
'Aaron Suggs',
'Brian Hartsock',
'Christopher Oliver',
'Dylan Egan',
'geemus',
'Henry Addison',
'Lincoln Stoll',
'Luqman Amjad',
'nightshade427',
'Patrick Debois',
'Wesley Beary'
].include?(committer)
next
end
changelog << "MVP! #{committer}"
changelog << ''
break
end
for tag in changes.keys.sort
changelog << ('[' << tag << ']')
for commit in changes[tag]
changelog << (' ' << commit)
end
changelog << ''
end
old_changelog = File.read('changelog.txt')
File.open('changelog.txt', 'w') do |file|
file.write(changelog.join("\n"))
file.write("\n\n")
file.write(old_changelog)
end
# Include Yard tasks for rake yard
YARDOC_LOCATION = "doc"
YARD::Rake::YardocTask.new do |t|
t.files = ['lib/**/*.rb', "README"]
t.options = ["--output-dir", YARDOC_LOCATION, "--title", "#{name} #{version}"]
end
task :docs do
Rake::Task[:supported_services_docs].invoke
Rake::Task[:upload_fog_io].invoke
Rake::Task[:upload_rdoc].invoke
# connect to storage provider
Fog.credential = :geemus
storage = Fog::Storage.new(:provider => 'AWS')
directory = storage.directories.new(:key => 'fog.io')
# write base index with redirect to new version
directory.files.create(
:body => redirecter('latest'),
:content_type => 'text/html',
:key => 'index.html',
:public => true
)
Formatador.display_line
end
task :supported_services_docs do
support, shared = {}, []
for key, values in Fog.services
unless values.length == 1
shared |= [key]
values.each do |value|
support[value] ||= {}
support[value][key] = '+'
end
else
value = values.first
support[value] ||= {}
support[value][:other] ||= []
support[value][:other] << key
end
end
shared.sort! {|x,y| x.to_s <=> y.to_s}
columns = [:provider] + shared + [:other]
data = []
for key in support.keys.sort {|x,y| x.to_s <=> y.to_s}
data << { :provider => key }.merge!(support[key])
end
table = ''
table << "<table border='1'>\n"
table << " <tr>"
for column in columns
table << "<th>#{column}</th>"
end
table << "</tr>\n"
for datum in data
table << " <tr>"
for column in columns
if value = datum[column]
case value
when Array
table << "<td>#{value.join(', ')}</td>"
when '+'
table << "<td style='text-align: center;'>#{value}</td>"
else
table << "<th>#{value}</th>"
end
else
table << "<td></td>"
end
end
table << "</tr>\n"
end
table << "</table>\n"
File.open('docs/about/supported_services.markdown', 'w') do |file|
file.puts <<-METADATA
---
layout: default
title: Supported Services
---
METADATA
file.puts(table)
end
end
task :upload_fog_io do
# connect to storage provider
Fog.credential = :geemus
storage = Fog::Storage.new(:provider => 'AWS')
directory = storage.directories.new(:key => 'fog.io')
# build the docs locally
sh "jekyll docs docs/_site"
# write web page files to versioned 'folder'
for file_path in Dir.glob('docs/_site/**/*')
next if File.directory?(file_path)
file_name = file_path.gsub('docs/_site/', '')
key = '' << version << '/' << file_name
Formatador.redisplay(' ' * 128)
Formatador.redisplay("Uploading [bold]#{key}[/]")
if File.extname(file_name) == '.html'
# rewrite links with version
body = File.read(file_path)
body.gsub!(/vX.Y.Z/, 'v' << version)
body.gsub!(/='\//, %{='/} << version << '/')
body.gsub!(/="\//, %{="/} << version << '/')
content_type = 'text/html'
directory.files.create(
:body => redirecter(key),
:content_type => 'text/html',
:key => 'latest/' << file_name,
:public => true
)
else
body = File.open(file_path)
content_type = nil # leave it up to mime-types
end
directory.files.create(
:body => body,
:content_type => content_type,
:key => key,
:public => true
)
end
Formatador.redisplay(' ' * 128)
Formatador.redisplay("Uploaded docs/_site\n")
end
task :upload_rdoc do
# connect to storage provider
Fog.credential = :geemus
storage = Fog::Storage.new(:provider => 'AWS')
directory = storage.directories.new(:key => 'fog.io')
# write rdoc files to versioned 'folder'
Rake::Task[:rdoc].invoke
for file_path in Dir.glob('rdoc/**/*')
next if File.directory?(file_path)
file_name = file_path.gsub('rdoc/', '')
key = '' << version << '/rdoc/' << file_name
Formatador.redisplay(' ' * 128)
Formatador.redisplay("Uploading [bold]#{key}[/]")
directory.files.create(
:body => File.open(file_path),
:key => key,
:public => true
)
end
Formatador.redisplay(' ' * 128)
directory.files.create(
:body => redirecter("#{version}/rdoc/index.html"),
:content_type => 'text/html',
:key => 'latest/rdoc/index.html',
:public => true
)
Formatador.redisplay("Uploaded rdoc\n")
end
def redirecter(path)
redirecter = <<-HTML
<!doctype html>
<head>
<title>fog</title>
<meta http-equiv="REFRESH" content="0;url=http://fog.io/#{path}">
</head>
<body>
<a href="http://fog.io/#{path}">redirecting to lastest (#{path})</a>
</body>
</html>
HTML
end
require "tasks/changelog_task"
Fog::Rake::ChangelogTask.new

View file

@ -23,10 +23,8 @@ end
if ARGV.length > 1
require 'multi_json'
result = instance_eval(ARGV[1..-1].join(' '))
puts(MultiJson.encode(result))
puts(Fog::JSON.encode(result))
else
@ -39,6 +37,10 @@ else
@irb.context.prompt_mode = :FOG
@irb.context.workspace = IRB::WorkSpace.new(binding)
trap 'INT' do
@irb.signal_handle
end
Formatador.display_line('Welcome to fog interactive!')
Formatador.display_line(":#{Fog.credential} provides #{providers}")
providers = Fog.providers

File diff suppressed because it is too large Load diff

View file

@ -1,39 +0,0 @@
safe: false
auto: false
server: false
server_port: 4000
source: .
destination: ./_site
plugins: ./_plugins
future: true
lsi: false
pygments: false
markdown: maruku
permalink: none
maruku:
use_tex: false
use_divs: false
png_engine: blahtex
png_dir: images/latex
png_url: /images/latex
rdiscount:
extensions: []
kramdown:
auto_ids: true,
footnote_nr: 1
entity_output: as_char
toc_levels: 1..6
use_coderay: false
coderay:
coderay_wrap: div
coderay_line_numbers: inline
coderay_line_numbers_start: 1
coderay_tab_width: 4
coderay_bold_every: 10
coderay_css: style

View file

@ -1,111 +0,0 @@
<!doctype html>
<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>fog - {{ page.title }}</title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--
<link rel="shortcut icon" href="/public/favicon.ico">
<link rel="apple-touch-icon" href="/public/apple-touch-icon.png">
-->
<link rel="stylesheet" href="/public/css/style.css?v=2">
<link rel="stylesheet" href="/public/css/fog.css?v=2">
<script src="/public/js/libs/modernizr-1.6.min.js"></script>
</head>
<body>
<div id="container">
<header>
<a href="/"><img src="/public/images/fog.png" title="fog" /></a>
<h1>{{ page.title }}</h1>
<dl>
<dt>version</dt><dd>vX.Y.Z</dd>
<dt>install</dt><dd><code>gem install fog</code></dd>
<dt>source</dt><dd><a href="http://github.com/geemus/fog">geemus/fog</a></dd>
</dl>
</header>
<div id="main">
{{ content }}
<h2>Services</h2>
<ul>
<li><a href="/cdn">CDN</a></li>
<li><a href="/compute">Compute</a></li>
<li><a href="/dns">DNS</a></li>
<li><a href="/storage">Storage</a></li>
{% for post in site.posts %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
<h2>About</h2>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about/contributing.html">Contributing</a></li>
<li><a href="/about/getting_started.html">Getting Started</a></li>
<li><a href="/about/press.html">Press</a></li>
<li><a href="/about/structure.html">Structure</a></li>
<li><a href="/about/supported_services.html">Supported Services</a></li>
<li><a href="/about/users.html">Users</a></li>
</ul>
</div>
<footer>
sponsored by
<img height="20px" src="public/images/engineyard.png" title="engineyard" />
</footer>
</div> <!-- end of #container -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
<script>!window.jQuery && document.write(unescape('%3Cscript src="public/js/libs/jquery-1.4.2.js"%3E%3C/script%3E'))</script>
<!-- scripts concatenated and minified via ant build script-->
<script src="public/js/plugins.js"></script>
<script src="public/js/script.js"></script>
<!-- end concatenated and minified scripts-->
<!--[if lt IE 7 ]>
<script src="public/js/libs/dd_belatedpng.js"></script>
<script> DD_belatedPNG.fix('img, .png_bg'); </script>
<![endif]-->
<!-- yui profiler and profileviewer - remove for production -->
<script src="public/js/profiling/yahoo-profiling.min.js"></script>
<script src="public/js/profiling/config.js"></script>
<!-- end profiling code -->
<!-- change the UA-XXXXX-X to be your site's ID -->
<script>
var _gaq = [['_setAccount', 'UA-301159-7'], ['_trackPageview']];
(function(d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.async = true;
g.src = ('https:' == location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
s.parentNode.insertBefore(g, s);
})(document, 'script');
</script>
</body>
</html>

View file

@ -1,228 +0,0 @@
---
layout: default
title: Contributing
---
First off, high five for coming to visit this page. You are my new hero.
## Overview
* Organize your patches by keeping all related changes together in a topic branch.
* Rebase your branch against master before submitting a pull request (and squish any 'oops' or work in progress commits).
* Submit changes as pull requests describing what the changes should cover and referencing issues (if any).
* Use 'tags' in your commits to indicate the scope, so things like '\[aws|compute\] fixed something'.
* Write and run tests. Tests should follow through usage workflows and ought to pass both with mocking on and off.
## Deep dive
Now then, some of the what makes it tick and why. For simplicity let's pretend you want to implement a new service, from scratch. I will walk through the requisite pieces and important things to keep in mind as you go.
But, before I dive too deep, I'll leave you with an out. Other great ways to contribute are fixing bugs, writing documentation or helping port other projects to use fog. That way everybody wins!
## The Service
First and foremost you'll need to create a service, which should start from something like:
module Fog
class TheService < Fog::Service
requires :necessary_credential
model_path 'path/to/models'
collection 'name_of_collection'
model 'name_of_model'
request_path 'path/to/requests'
request 'name_of_request'
class Mock
include Collections
end
class Real
include Collections
end
end
end
### Highlights:
* we segregate between real and mock so it is easier to add stuff to one or the other later.
* this is where any shared stuff will go, like making/signing requests
## Requests
The next thing to bite off are the requests. fog is all about making cloud services easier to use and move between, but requests are not where this happens. Requests should map closely to the actual api requests (you should be able to directly reference the api docs and vice versa). In particular, try to keep the output of any data parsing as close to the actual format as possible. This makes implementation and maintenance much easier and provides a solid foundation for models to build nice things on top of. I generally end up working on stuff to get/list details first and then filling in create/destroy pairs and other requests.
You start with something like this:
<pre>
module Fog
class TheService
class Real
def request(*args)
end
end
class Mock
def request(*args)
Fog::Mock.not_implemented
end
end
end
end
</pre>
### Highlights:
* You should define the method twice, once for the real implementation and once for mocked (they should take the same arguments).
* The mock versions should just start out by raising a not implemented error, you can come back and fill this in later.
* The real version should make a request, probably by a method defined on the real class in the service you defined earlier.
* Each request should either return an Excon::Response (with a parsed body where appropriate) or raise an error.
## Tests
Now would be a good time to write some tests to make sure what you have written works (and will continue to). I've tried a couple variations on testing in the past, but have settled on consolidated lifetime testing. These vary enough that its hard to give a single simple example, but you can see many examples in "tests/compute/requests/aws":https://github.com/geemus/fog/tree/master/tests/compute/requests/aws/.
### Highlights:
* Reuse the same objects and take them through their whole life cycle (this is much faster, and most of the time if one portion fails the others would anyway).
* Test the format of the output to ensure parsers match expectations (check the provider's api docs) and that mocks return matching data.
* Test common failure cases and their behavior, you'll need to know how the service acts in these cases to make better mocks.
## Models
You could also skip to the mocks here if you wanted, but I usually find the more time I spend working with the service the easier it is to build mocks. The models are the real pay dirt, you have slogged through low level requests that map to the provider api and now you want a nice interface. This is where models and collections come in. Collections provide access to lists of data on the provider and for creating new objects. Models represent the individual objects.
If you know which object you'd like to represent you should start with the collection. When naming, please refer to the names that have been chosen for other services. I haven't standardized all nouns yet, but a few are already shared (Flavor, Image, Server)
An example servers collection:
require 'fog/collection'
require 'fog/theservice/models/server'
module Fog
class TheService
class Servers < Fog::Collection
model Fog::TheService::Server
def all
# get list of servers
load(data) # data is an array of attribute hashes
end
def get(identity)
# get server matching id
new(data) # data is an attribute hash
rescue Excon::Errors::NotFound
nil
end
end
end
end
### Highlights
* First make an accessor in the Collections model so it will be included in Real and Mock.
* `#model` will take a reference to the class that will be instantiated to represent individual objects.
* `#all` should get a list of servers from the provider and pass an array of attribute hashes, one per server, to load.
* `#get` should take an identity reference and instantiate a new model object with an attribute hash returned from the remote server, or return nil of no such object exists.
Models handle remapping attributes into friendlier names and providing the rest of the interface.
An example model:
require 'fog/model'
module Fog
module TheService
class Server << Fog::Model
identity :id
attribute :state, 'StatusValue'
def destroy
requires :identity
connection.destroy_server(identity)
true
end
def ready?
state == 'running'
end
def save
requires ...
connection.create_server(options)
true
end
end
end
end
### Highlights
* `#identity` captures the id/name that the objects are identified by and takes the same arguments as attribute.
* `#attribute` takes the name to make a variable available as and one or more aliases that parsers/requests will return this value as.
* `#destroy` will require the identity of the model and should destroy it and return true.
* `#ready?` should return whether the object has finished being initialized (where appropriate).
* `#save` should take any required objects and instantiate the object on the provider's service.
* These models just rely on underlying collections and requests, so it should not be necessary at this level to distinguish between Real and Mock methods.
## Mocks
Mocks provide a powerful tool for users of fog to experiment with their implementations much more quickly and without incurring costs. I usually save these for last, as implementing the requests and models provide some necessary context to finally put the mocks together. Your services mock class should have a data method that will return mocked data like so:
module Fog
module TheService
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {}
end
end
end
end
end
The keys in this hash should represent a unique identifier of the user accessing the data and the value assigned should contain any default data that a new user might have. Any implemented mock requests should then return data retrieved from here or raise an error.
For instance:
module Fog
module TheService
class Mock
def destroy_server(server_identity)
if data = self.data[:servers].delete(server_identity)
response = Excon::Response.new
response.status = 202
response.body = data
response
else
raise Fog::TheService::NotFound
end
end
end
end
end
### Highlights
* Mock requests should return the same type of data as an already parsed real response or should return the same error as a real problem.
* By mocking at this low level, higher level functions are automatically mocked out for you.
* The extra rigorous tests related to output formatting and error messages should help keep you honest, and each should pass in both mocked and unmocked modes.
## Summary
That provides a lot more detail than you will probably need right away, but hopefully you can refer back to different sections as you need them. If you have any questions send me a github message or email me (address is on my profile). You should always start development by creating your own fork. When you feel confident about your fork, send me a pull request. Be forewarned that I may edit some things before it gets to master, but I'll do my best to take care of this in a timely manner.
Thanks again for your interest and let me know if there is anything else I can do to help.

View file

@ -1,83 +0,0 @@
---
layout: default
title: Getting Started
---
First off, install the gem:
$ gem install fog
## Setting Up Local Storage
We will be using Local storage in the example. Local storage provides the same api that cloud storage services in fog do, but without the bother of needing to signup for stuff or pay extra money.
First, make a local directory to hold your data.
$ mkdir ~/fog
Now we can start writing our script, first off we should require fog.
require 'rubygems'
require 'fog'
Now in order to play with our data we need to setup a storage connection.
storage = Fog::Storage.new({
:local_root => '~/fog',
:provider => 'Local'
})
`storage` will now contain our storage object, configured to use the Local provider from our specified directory.
## Storing Data
Now that you have cleared the preliminaries you are ready to start storing data. Storage providers in fog segregate files into `directories` to make it easier to organize things. So lets create a directory so we can see that in action.
directory = storage.directories.create(
:key => 'data'
)
To make sure it was created you can always check in your filesystem, but we can also check from inside fog.
storage.directories
Progress! Now it is time to actually create a file inside our new directory.
file = directory.files.create(
:body => 'Hello World!',
:key => 'hello_world.txt'
)
We should now have our file, first we can open it up and make sure we are on the right track.
$ open ~/fog/hello_world.txt
It is much more likely that you will want to see what files you have from inside fog though.
directory.files
Now that we have run through all the basics, lets clean up our mess.
file.destroy
directory.destroy
After that you should be able to check your directory list in fog or your filesystem and see you are safely back to square one.
## Next Steps
Using the same interface you can also practice working against a real provider (such as Amazon S3). Rather than worrying about signing up for an account right away though, we can use mocks to simulate S3 while we practice.
This time we will turn on mocking and then, just like before, we will need to make a connection.
Fog.mock!
storage = Fog::Storage.new({
:aws_access_key_id => 'fake_access_key_id',
:aws_secret_access_key => 'fake_secret_access_key',
:provider => 'AWS'
})
You may notice that we used bogus credentials, this is fine since we are just simulating things. To use real S3 you can simply omit `Fog.mock!` and swap in your real credentials.
Once you have your connection you can go through all the steps you did before, only now you will be working against a real cloud service (or at least a simulated one).
Congratulations and welcome to the cloud! Continue your journey at [fog.io](http://fog.io)

View file

@ -1,52 +0,0 @@
---
layout: default
title: Press
---
Mentions and blog posts from elsewhere in reverse chronological order by day (and alphasorted for same days).
**September 13th, 2011**
* [Libvirt support for fog](http://jedi.be/blog/2011/09/13/libvirt-fog-provider/)
**August 1st, 2011**
* [Using EBS Snapshots with Fog](http://www.mediamolecule.com/lab/article/using_ebs_snapshots_with_fog/)
**June 21st, 2011**
* [Mocking fog When Using It With Carrierwave](http://www.engineyard.com/blog/2011/mocking-fog-when-using-it-with-carrierwave/)
**June 14th, 2011**
* [Backing Up Your Data With Fog](http://larrywright.me/blog/articles/221-backing-up-your-data-with-fog)
**April 7th, 2011**
* [Testing multipart Uploads to S3 with Threads](http://blog.vicecity.co.uk/post/4425574978/multipart-uploads-fog-threads-win)
**March 9th, 2011**
* [Offsite Backups with fog](http://www.engineyard.com/blog/2011/offsite-backups-with-fog/)
**March 2nd, 2011**
* [Better AWS Access Control with IAM and Fog](http://blog.zerosum.org/2011/03/02/better-aws-access-control-with-iam-and-fog.html)
* [Using Amazon's CloudFormation, cloud-init, chef and fog to automate infrastructure](http://allanfeid.com/content/using-amazons-cloudformation-cloud-init-chef-and-fog-automate-infrastructure)
**January 6th, 2011**
* [Happy New Year (and 0.4.0) from fog!](http://www.engineyard.com/blog/2011/happy-new-year-and-0-4-0-from-fog/)
**November 30th, 2010**
* [Getting Hired: fog Edition](http://www.engineyard.com/blog/2010/getting-hired-fog-edition/)
**October 13, 2010**
* [Engine Yard Announces Formal Support for fog to Ensure Application Portability in the Cloud](http://www.engineyard.com/company/press/2010-10-13-engine-yard-announces-formal-support-for-%E2%80%98fog%E2%80%99-to-ensure-application-portability-in-the-cloud)
* [Wesley Beary and fog Promoted to the Engine Yard Open Source Program](http://www.engineyard.com/blog/2010/wesley-beary-and-fog-promoted-to-the-engine-yard-open-source-program/)
**September 28, 2010**
* [The Curious Tale of the Humble Micro](http://www.engineyard.com/blog/2010/the-curious-tale-of-the-humble-micro/)

View file

@ -1,78 +0,0 @@
---
layout: default
title: Structure
---
fog is the Ruby cloud computing library, top to bottom:
* Collections provide a simplified interface, making clouds easier to work with and switch between.
* Requests allow power users to get the most out of the features of each individual cloud.
* Mocks make testing and integrating a breeze.
## Collections
A high level interface to each cloud is provided through collections, such as `images` and `servers`.
You can see a list of available collections by calling `collections` on the connection object. You can try it out using the `fog` command:
>> AWS.collections
[:addresses, :directories, ..., :volumes, :zones]
Some collections are available across multiple providers:
* compute providers have `flavors`, `images` and `servers`
* dns providers have `zones` and `records`
* storage providers have `directories` and `files`
Collections share basic CRUD type operations, such as:
* `all` - fetch every object of that type from the provider.
* `create` - initialize a new record locally and a remote resource with the provider.
* `get` - fetch a single object by it's identity from the provider.
* `new` - initialize a new record locally, but do not create a remote resource with the provider.
As an example, we'll try initializing and persisting a Rackspace Cloud server:
require 'fog'
compute = Fog::Compute.new({
:provider => 'Rackspace',
:rackspace_api_key => key,
:rackspace_username => username
})
# boot a gentoo server (flavor 1 = 256, image 3 = gentoo 2008.0)
server = compute.servers.create(:flavor_id => 1, :image_id => 3, :name => 'my_server')
server.wait_for { ready? } # give server time to boot
# DO STUFF
server.destroy # cleanup after yourself or regret it, trust me
## Models
Many of the collection methods return individual objects, which also provide common methods:
* `destroy` - will destroy the persisted object from the provider
* `save` - persist the object to the provider
* `wait_for` - takes a block and waits for either the block to return true for the object or for a timeout (defaults to 10 minutes)
## Requests
Requests allow you to dive deeper when the models just can't cut it.
You can see a list of available requests by calling #requests on the connection object.
For instance, ec2 provides methods related to reserved instances that don't have any models (yet). Here is how you can lookup your reserved instances:
$ fog
>> AWS[:ec2].describe_reserved_instances
#<Excon::Response [...]>
It will return an [excon](http://github.com/geemus/excon) response, which has `body`, `headers` and `status`. Both return nice hashes.
## Mocks
As you might imagine, testing code using Fog can be slow and expensive, constantly turning on and and shutting down instances.
Mocking allows skipping this overhead by providing an in memory representation resources as you make requests.
Enabling mocking easy to use, before you run other commands, simply run:
Fog.mock!
Then proceed as usual, if you run into unimplemented mocks fog will raise an error and as always contributions are welcome!

View file

@ -1,33 +0,0 @@
---
layout: default
title: Users
---
Here lies a listing of projects and products that are using fog.
Please feel free to add your own, just please follow these rules for consistency and readability.
1. Listings should be in alphabetical order and should have a link and list of services used.
2. Projects that are open source should link to where the source code can be found.
3. Products that are not open source should link to where more information about the product can be found.
Thanks for following these rules to keep the quality high and and the content useful!
## Projects
* [carrierwave](http://github.com/jnicklas/carrierwave) = AWS => Storage
* [chef](http://github.com/opscode/chef) = AWS => Compute, Slicehost => Compute, Terremark => vCloud, Rackspace => Compute
* [deckard](http://github.com/joewilliams/deckard) = AWS => Compute
* [gaff](http://github.com/joewilliams/gaff) = AWS => Compute, Slicehost => Compute
* [gemcutter](http://github.com/rubygems/gemcutter) = AWS => Storage
* [plover](http://github.com/railsmachine/plover) = AWS => Compute
## Products
* [DevStructure](http://devstructure.com/) = AWS => Compute, Rackspace => Compute, Slicehost => Compute
* [Engine Yard AppCloud](http://www.engineyard.com/cloud) = AWS => \[Compute, Storage\]
* [iSwifter](http://iswifter.youwebinc.com/) = BlueBox => Compute
* [OpenFeint](http://openfeint.com) = BlueBox => Compute
* [PHPFog](https://phpfog.com) = AWS => Compute
* [RowFeeder](https://rowfeeder.com) = Blue Box Group => Compute
* [Viximo](http://viximo.com) = AWS => Compute

View file

@ -1,82 +0,0 @@
---
layout: default
title: CDN
---
Faster websites are better. <a href="http://www.websiteoptimization.com/speed/tweak/design-factors/">Better experience</a>, <a href="http://exp-platform.com/Documents/IEEEComputer2007OnlineExperiments.pdf">better sales</a>, <a href="http://www.stevesouders.com/blog/2009/07/27/wikia-fast-pages-retain-users/">you name it</a>. Unfortunately, making a website faster can be tough. Thankfully a content distribution network, or CDN, can give you great performance bang for your buck. A CDN helps speed things up by putting copies of your files closer to your users. It's like the difference between pizza delivery from across the street and pizza delivery from the next town over.
The ease and deliciousness are the good news, but until recently CDN's were only available in the big leagues via 'my business guys will talk to your business guys' deals. Fortunately for us, Amazon recently updated <a href="http://aws.amazon.com/cloudfront/">CloudFront</a>, their CDN service, to allow us to get these benefits with just a credit card and an API call. So now we'll see how you can spend a few minutes to save your users countless hours of load time.
## Preliminaries
First, make sure you have fog installed:
gem install fog
Now you'll need to <a href="https://aws-portal.amazon.com/gp/aws/developer/subscription/index.html?productCode=AmazonCloudFront">sign up for Cloudfront</a>. Gather up the credentials your new credentials to initialize a connection to the service:
require 'fog'
# create a connection to the service
cdn = Fog::CDN.new({
:provider => 'AWS',
:aws_access_key_id => AWS_ACCESS_KEY_ID,
:aws_secret_access_key => AWS_SECRET_ACCESS_KEY
}
## Setting Up Your CDN
Now you'll need to create a 'distribution' which represents a mapping from the CDN to your domain. For the examples we'll pretend we are working on 'http://www.example.com', but you can just switch it to your actual domain. Some <a href="http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/CreateDistribution.html">other options</a> are available, but the only other one we need to fill in is OriginProtocolPolicy. This sets what to do about http vs https. We will use 'match-viewer' which returns the same protocol as the request, but you can also choose 'http-only' which always returns http responses.
data = cdn.post_distribution({
'CustomOrigin' => {
'DNSName' => 'www.example.com',
'OriginProtocolPolicy' => 'match-viewer'
}
})
# parse the response for stuff you'll need later
distribution_id = data.body['Id']
caller_reference = data.body['CallerReference']
etag = data.headers['ETag']
cdn_domain_name = data.body['DomainName']
# wait for the updates to propogate
Fog.wait_for {
cdn.get_distribution(distribution_id).body['Status'] ## 'Deployed'
}
## Getting Served
With the domain name from the distribution in hand you should now be ready to serve content from the edge. All you need to do is start replacing urls like `http://www.example.com/stylesheets/foo.css` with `#{cdn_domain_name}/stylesheets/foo.css`. Just because you can do something doesn't always mean you should though. Dynamic pages are not really well suited to CDN storage, since CDN content will be the same for every user. Fortunately some of your most used content is a great fit. By just switching over your images, javascripts and stylesheets you can have an impact for each and every one of your users.
Congrats, your site is faster! By default the urls aren't very pretty, something like `http://d1xdx2sah5udd0.cloudfront.net/stylesheets/foo.css`. Thankfully you can use CNAME config options to utilize something like `http://assets.example.com/stylesheets/foo.css`, if you are interested in learning more about this let me know in the comments.
## Cleaning Up
But, just in case you need to update things I'll run through how you can make changes. In my case I just want to clean up after myself, so I'll use the distribution_id and ETag from before to disable the distribution. We need to use the ETag as well because it provides a way to refer to different versions of the same distribution and ensures we are updating the version that we think we are.
data = cdn.put_distribution_config(
distribution_id,
etag,
{
'CustomOrigin' => {
'DNSName' => 'www.example.com',
'OriginProtocolPolicy' => 'match-viewer'
},
'CallerReference' => caller_reference,
'Enabled' => 'false'
}
)
# parse the updated etag
etag = data.headers['ETag']
Now you just need to wait for the update to happen like before and once its disabled we can delete it:
Fog.wait_for {
cdn.get_distribution(distribution_id).body['Status'] ## 'Deployed'
}
cdn.delete_distribution(distribution_id, etag)
Thats it, now go forth and speed up some load times!

View file

@ -1,108 +0,0 @@
---
layout: default
title: Compute
---
Compute is the lifeblood of the cloud, but with great power comes great complication. Compute opens up huge swaths of potential, but it varies greatly in both capabilities and usage from provider to provider. Thankfully fog helps to abstract these idiosyncrasies to provide a more seamless experience.
## Installing fog
fog is distributed as a RubyGem:
gem install fog
Or for bundler users, you can add it in your Gemfile:
gem "fog"
## Using Amazon EC2 and fog
Sign up for an account <a href="http://aws-portal.amazon.com/gp/aws/developer/subscription/index.html?productCode=AmazonEC2">here</a> and copy down your secret access key and access key id from <a href="http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key">here</a>. We are about to get into the code samples, so be sure to fill in anything in ALL_CAPS with your own values!
First, create a connection with your new account:
require 'rubygems'
require 'fog'
# create a connection
connection = Fog::Compute.new({
:provider => 'AWS',
:aws_secret_access_key => YOUR_SECRET_ACCESS_KEY,
:aws_access_key_id => YOUR_SECRET_ACCESS_KEY_ID
})
With that in hand we are ready to start making EC2 calls!
## Servers the EC2 way
Creating a server on EC2 is very easy if you are willing to accept the defaults (the smallest server size, using Ubuntu 10.04 LTS). NOTE: the default EC2 image uses the 'ubuntu' username, rather than 'root' like other services.
server = connection.servers.create
You can then list your servers to see that it now appears:
connection.servers
Rather than worrying about the whole list, we can also just get the latest data for just our server:
server.reload
That can get tedious quickly however, especially when servers can take several minutes to boot. Fog has `wait_for` for cases like this and `ready?` for checking to see when a server has completed its start up.
server.wait_for { ready? }
Once we are done with that we can shut it down.
server.destroy
## Bootstrap: Servers the fog Way
Cycling servers is great, but in order to actually ssh in we need to setup ssh keys and open ports. But rather than worrying about the nitty gritty, we will utilize `bootstrap`. NOTE: normally we could leave out username and use the default (root), but the default Ubuntu from Canonical uses the ubuntu username instead.
server = connection.servers.bootstrap(:private_key_path => '~/.ssh/id_rsa', :public_key_path => '~/.ssh/id_rsa.pub', :username => 'ubuntu')
Bootstrap will create the server, but it will also make sure that port 22 is open for traffic and has ssh keys setup. In order to hook everything up it will need the server to be running, so by the time it finishes it will be ready. You can then make commands to it directly:
server.ssh('pwd')
server.ssh(['pwd', 'whoami'])
These return an array of results, where each has stdout, stderr and status values so you can check out what your commands accomplished. Now just shut it down to make sure you don't continue getting charged.
server.destroy
## Rackspace Cloud Servers
Rackspace has <a href="http://www.rackspacecloud.com/cloud_hosting_products/servers">Cloud Servers</a> and you can sign up <a href="https://www.rackspacecloud.com/signup">here</a> and get your credentials <a href="https://manage.rackspacecloud.com/APIAccess.do">here</a>.
# create a connection
connection = Fog::Compute.new({
:provider => 'Rackspace',
:rackspace_username => RACKSPACE_USERNAME,
:rackspace_api_key => RACKSPACE_API_KEY
})
If you work with the European cloud from Rackspace you have to add the following:
:rackspace_auth_url => "lon.auth.api.rackspacecloud.com"
We will skip over learning how to do this the 'Rackspace Way' and instead jump right to using bootstrap to get their smallest Ubuntu 10.04 LTS server.
server = connection.servers.bootstrap
You can run all the same ssh commands and do what you need to, then once again shutdown to ensure you are not charged once you are done.
server.destroy
## Mocking out Compute
You can also start any of these scripts with `Fog.mock!` or start the fog interactive tool from the command line with `FOG_MOCK=true fog` to run in mock mode. In this mode commands are run as local simulation, so no cloud resources are ever consumed and things operate much faster.
## Cleaning up
To cover your tracks its a good idea to check for running servers and shut them down, here is one way you might do that.
connection.servers.select {|server| server.ready? && server.destroy}
## Summary
Compute can be tricky, but the abstractions in fog make it much easier to get started. With your servers up and running you can then focus on the task at hand and get some work done. Congratulations on adding a new tool to your arsenal and let us know what we can do better.

View file

@ -1,79 +0,0 @@
---
layout: default
title: DNS
---
The power and flexibility of the cloud are amazing. But sometimes it can be a pain to chase your resources around and keep everything up to date. This is especially true of keeping track of addresses for DNS, but thankfully more and more API driven options are available, allowing you to automate your DNS to keep up with your hardware changes.
## Setup
First, make sure you have fog installed:
gem install fog
For this first example we will use Zerigo (see below for how to use other providers). You can signup for Zerigo DNS <a href="https://www.zerigo.com/signup/dns">here</a>. Gather up your new credentials to initialize a connection to the service:
require 'rubygems'
require 'fog'
# create a connection to the service
dns = Fog::DNS.new({
:provider => 'Zerigo',
:zerigo_email => ZERIGO_EMAIL,
:zerigo_token => ZERIGO_TOKEN
})
## Getting in the Zone
The first thing you need to do to prepare for your DNS excursion is create a zone for your domain. The zone will contain all of the more specific records that you will create later. You will just need to specify the domain, which should be your url without the 'http' or 'www' parts, and an email address. Then you can create the zone with your DNS connection:<!--more-->
zone = @dns.zones.create(
:domain => 'example.com',
:email => 'admin@example.com'
)
Now that you have a zone you will need to update your registrar to let them know what DNS servers are responsible for your domain. You can ask the zone what values to use:
zone.nameservers
## Spinning Records
With your new zone in hand you can add records as needed. First and foremost you will probably want the 'www' version of your site to point to whatever your ip might be:
record = @zone.records.create(
:ip => '1.2.3.4',
:name => 'example.com',
:type => 'A'
)
Adding other records is similarly easy, for instance if we want 'www.example.com' to go to the same place, we can use a cname record:
record = @zone.records.create(
:ip => 'example.com',
:name => 'www',
:type => 'CNAME'
)
Or, similarly you might want to have your blog elsewhere:
record = @zone.records.create(
:ip => '4.3.2.1',
:name => 'blog.example.com',
:type => 'A'
)
You can add more specifics if you need to, but reasonable defaults make it just that easy. You can also add any other kind of DNS record you might need for mail or other purposes, you can find a nice overview of record options and types <a href="http://en.wikipedia.org/wiki/Domain_Name_System#DNS_resource_records">on Wikipedia</a>.
## No Zerigo? No Problem
If you already have an account with another service you can just as easily use this same code with different credentials. fog currently supports <a href="http://aws.amazon.com/route53/">AWS Route 53</a>, <a href="http://bluebox.net">Blue Box</a>, <a href="http://dnsimple.com">DNSimple</a>, <a href="http://www.linode.com">Linode</a>, <a href="http://www.rackspace.com">Rackspace</a>, <a href="http://www.slicehost.com">Slicehost</a> and <a href="http://www.zerigo.com/managed-dns">Zerigo</a>; so you can have your pick. As an example you can connect to AWS instead of Zerigo:
dns = Fog::DNS.new({
:provider => 'AWS',
:aws_access_key_id => AWS_ACCESS_KEY_ID,
:aws_secret_access_key => AWS_SECRET_ACCESS_KEY
})
## Go Forth and Resolve
You can see an example of reusing code like this in the <a href="https://github.com/geemus/fog/blob/master/examples/dns_tests.rb">examples folder</a>. Using this makes it easier to give yourself shortcuts to your cloud servers and manage how clients and users access them as well. It is great to have this flexibility so that you can modify your cloud infrastructure as needed while keeping everything ship shape. It also provides a nice way to create custom subdomains for users and just generally round out your cloud solution.

View file

@ -1,96 +0,0 @@
---
layout: default
title: The Ruby cloud services library
---
Whether you need compute, dns, storage, or a multitude of other services, fog provides an accessible entry point and facilitates cross service compatibility.
Just getting started working with cloud resources? You are not alone, and having so many complicated options makes it hard to know where to start. fog delivers the knowledge of cloud experts to you, helping you to bootstrap your cloud usage and guiding you as your own expertise develops.
By coding with fog from the start you avoid vendor lock-in and give yourself more flexibility to provide value. Whether you are writing a library, designing a software as a service product or just hacking on the weekend this flexibility is a huge boon.
With a rapidly expanding community and codebase the advantages of fog just keep coming. Join us and together we will realize the future of cloud computing.
## Getting Started
sudo gem install fog
Now type 'fog' to try stuff, confident that fog will let you know what to do. Here is an example of wading through server creation for Amazon Elastic Compute Cloud:
>> server = Compute[:aws].servers.create
ArgumentError: image_id is required for this operation
>> server = Compute[:aws].servers.create(:image_id => 'ami-5ee70037')
<Fog::AWS::EC2::Server [...]>
>> server.destroy # cleanup after yourself or regret it, trust me
true
## Go forth and conquer
Play around and use the console to explore or check out the [getting started guide](/about/getting_started.html) for more details. Once you are reading to start scripting fog, here is a quick hint on how to make connections without the command line thing to help you.
# create a compute connection
compute = Fog::Compute.new({:provider => 'AWS', :aws_access_key_id => ACCESS_KEY_ID, :aws_secret_access_key => SECRET_ACCESS_KEY})
# compute operations go here
# create a storage connection
storage = Fog::Storage.new({:provider => 'AWS', :aws_access_key_id => ACCESS_KEY_ID, :aws_secret_access_key => SECRET_ACCESS_KEY})
# storage operations go here
geemus says: "That should give you everything you need to get started, but let me know if there is anything I can do to help!"
## Contributing
* Find something you would like to work on. For suggestions look for the `easy`, `medium` and `hard` tags in the [issues](http://github.com/geemus/fog/issues)
* Fork the project and do your work in a topic branch.
* Add shindo tests to prove your code works and run all the tests using `bundle exec rake`.
* Rebase your branch against geemus/fog to make sure everything is up to date.
* Commit your changes and send a pull request.
## T-Shirts
Wonder how you can get a lovely fog shirt? Look no further!
* Blue shirts go to people who have contributed indirectly, great examples are writing blog posts or giving lightning talks.
* Grey shirts and a follow from @fog go to people who have made it on to the [contributors list](https://github.com/geemus/fog/contributors) by submitting code.
* Black shirts go to people who have made it on to the [collaborators list](https://github.com/api/v2/json/repos/show/geemus/fog/collaborators) by coercing geemus into adding them (geemus is currently the only member of this list).
## Resources
Enjoy, and let me know what I can do to continue improving fog!
* Work through the [fog tutorial](https://github.com/downloads/geemus/learn_fog/learn_fog.tar.gz)
* Read fog's [API documentation](/rdoc)
* Stay up to date by following [@fog](http://twitter.com/fog) and/or [@geemus](http://twitter.com/geemus) on Twitter.
* Get and give help on the [#ruby-fog](irc://irc.freenode.net/ruby-fog) irc channel on Freenode
* Follow release notes and discussions on the [mailing list](http://groups.google.com/group/ruby-fog)
* Report bugs or find tasks to help with in the [issues](http://github.com/geemus/fog/issues)
* Learn about [contributing](/about/contributing.html)
* See where fog is used and let the world know how you use it [in the wild](/about/users.html)
* Check out blog posts and other mentions in the [press](/about/press.html)
## Copyright
(The MIT License)
Copyright (c) 2010 [geemus (Wesley Beary)](http://github.com/geemus)
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.

View file

@ -1,25 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
<!-- Most restrictive policy: -->
<site-control permitted-cross-domain-policies="none"/>
<!-- Least restrictive policy: -->
<!--
<site-control permitted-cross-domain-policies="all"/>
<allow-access-from domain="*" to-ports="*" secure="false"/>
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
-->
<!--
If you host a crossdomain.xml file with allow-access-from domain=“*”
and dont understand all of the points described here, you probably
have a nasty security vulnerability. ~ simon willison
-->
</cross-domain-policy>

View file

@ -1,129 +0,0 @@
/* layout */
body {
background-color: #EBF2F9;
line-height: 1.5em;
text-align: center;
}
#container {
margin: auto;
text-align: left;
width: 800px;
}
header {
background-color: #A0C0E1;
border-color: #70A1D2;
-moz-border-radius: 0 0 0.5em 0.5em;
border-radius: 0 0 0.5em 0.5em;
border-style: solid;
border-width: 0 1px 1px 1px;
color: #FFF;
height: 154px;
margin-bottom: 2em;
position: relative;
text-align: center;
}
header a, header a:active, header a:visited {
color: #FFF;
}
header img {
position: absolute;
left: 0;
top: 0;
}
header h1 {
font-size: 2em;
line-height: 154px;
}
header dl {
background-color: #70A1D2;
border-color: #70A1D2;
-moz-border-radius: 0 0 0.5em 0.5em;
border-radius: 0 0 0.5em 0.5em;
height: 140px; /* 154 - padding-top */
position: absolute;
padding: 14px 1.5em 0 1.5em;
right: 0;
top: 0;
}
header dl dt {
font-weight: bold;
}
#main {
background-color: #FFF;
-moz-border-radius: 0.5em;
border-radius: 0.5em;
color: #666;
border: 1px solid #70A1D2;
padding: 0 1em;
margin-bottom: 2em;
}
footer {
background-color: #A0C0E1;
border-color: #70A1D2;
-moz-border-radius: 0.5em 0.5em 0 0;
border-radius: 0.5em 0.5em 0 0;
border-style: solid;
border-width: 1px 1px 0 1px;
color: #FFF;
text-align: center;
}
footer img {
padding: 5px;
vertical-align: middle;
}
/* misc */
h2 {
border-bottom: 2px solid #A0C0E1;
color: #70A1D2;
font-size: 1.5em;
margin-top: 1.33333333333333em;
padding: 0 0.75em 0.75em 0;
}
code, pre {
background-color: #EBF2F9;
border: 1px solid #70A1D2;
color: #666;
}
code {
padding: 0 0.2em;
}
p, ul {
margin-bottom: 1em;
margin-top: 1em;
}
pre {
-moz-border-radius: 0.5em;
border-radius: 0.5em;
margin: 1em;
white-space: pre;
}
pre code {
border: none;
}
@media all and (orientation:portrait) {
}
@media all and (orientation:landscape) {
}
@media screen and (max-device-width: 480px) {
/* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */
}

View file

@ -1,8 +0,0 @@
* {
float: none;
background: #fff;
color: #000;
}
body { font-size: 80%; }

View file

@ -1,129 +0,0 @@
/* HTML5 ✰ Boilerplate */
html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure, footer, header, hgroup,
menu, nav, section, summary, time, mark, audio, video {
margin:0;
padding:0;
border:0;
outline:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
}
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
display:block;
}
nav ul { list-style:none; }
blockquote, q { quotes:none; }
blockquote:before, blockquote:after,
q:before, q:after { content:''; content:none; }
a { margin:0; padding:0; font-size:100%; vertical-align:baseline; background:transparent; }
ins { background-color:#ff9; color:#000; text-decoration:none; }
mark { background-color:#ff9; color:#000; font-style:italic; font-weight:bold; }
del { text-decoration: line-through; }
abbr[title], dfn[title] { border-bottom:1px dotted; cursor:help; }
table { border-collapse:collapse; border-spacing:0; }
hr { display:block; height:1px; border:0; border-top:1px solid #ccc; margin:1em 0; padding:0; }
input, select { vertical-align:middle; }
body { font:13px/1.231 sans-serif; *font-size:small; }
select, input, textarea, button { font:99% sans-serif; }
pre, code, kbd, samp { font-family: monospace, sans-serif; }
body, select, input, textarea { color: #444; }
h1,h2,h3,h4,h5,h6 { font-weight: bold; }
html { overflow-y: scroll; }
a:hover, a:active { outline: none; }
a, a:active, a:visited { color: #607890; }
a:hover { color: #036; }
ul, ol { margin-left: 1.8em; }
ol { list-style-type: decimal; }
nav ul, nav li { margin: 0; }
small { font-size: 85%; }
strong, th { font-weight: bold; }
td, td img { vertical-align: top; }
sub { vertical-align: sub; font-size: smaller; }
sup { vertical-align: super; font-size: smaller; }
pre { padding: 15px; white-space: pre; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; }
textarea { overflow: auto; }
.ie6 legend, .ie7 legend { margin-left: -7px; }
input[type="radio"] { vertical-align: text-bottom; }
input[type="checkbox"] { vertical-align: bottom; }
.ie7 input[type="checkbox"] { vertical-align: baseline; }
.ie6 input { vertical-align: text-bottom; }
label, input[type=button], input[type=submit], button { cursor: pointer; }
button, input, select, textarea { margin: 0; }
input:valid, textarea:valid { }
input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; }
.no-boxshadow input:invalid,
.no-boxshadow textarea:invalid { background-color: #f0dddd; }
::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; }
::selection { background:#FF5E99; color:#fff; text-shadow: none; }
a:link { -webkit-tap-highlight-color: #FF5E99; }
button { width: auto; overflow: visible; }
.ie7 img { -ms-interpolation-mode: bicubic; }
.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; }
.hidden { display: none; visibility: hidden; }
.visuallyhidden { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px, 1px, 1px, 1px); }
.invisible { visibility: hidden; }
.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; visibility: hidden; }
.clearfix:after { clear: both; }
.clearfix { zoom: 1; }
/* Primary Styles
Author:
*/
@media all and (orientation:portrait) {
}
@media all and (orientation:landscape) {
}
@media screen and (max-device-width: 480px) {
/* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */
}
@media print {
* { background: transparent !important; color: #444 !important; text-shadow: none !important; }
a, a:visited { color: #444 !important; text-decoration: underline; }
a:after { content: " (" attr(href) ")"; }
abbr:after { content: " (" attr(title) ")"; }
.ir a:after { content: ""; }
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
thead { display: table-header-group; }
tr, img { page-break-inside: avoid; }
@page { margin: 0.5cm; }
p, h2, h3 { orphans: 3; widows: 3; }
h2, h3{ page-break-after: avoid; }
}

View file

@ -1,3 +0,0 @@
*
!.gitignore

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,154 +0,0 @@
/*!
* jQuery JavaScript Library v1.4.2
* http://jquery.com/
*
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2010, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Sat Feb 13 22:33:48 2010 -0500
*/
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);

View file

@ -1,30 +0,0 @@
/*
* Modernizr v1.6
* http://www.modernizr.com
*
* Developed by:
* - Faruk Ates http://farukat.es/
* - Paul Irish http://paulirish.com/
*
* Copyright (c) 2009-2010
* Dual-licensed under the BSD or MIT licenses.
* http://www.modernizr.com/license/
*/
window.Modernizr=function(i,e,u){function s(a,b){return(""+a).indexOf(b)!==-1}function D(a,b){for(var c in a)if(j[a[c]]!==u&&(!b||b(a[c],E)))return true}function n(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1);c=(a+" "+F.join(c+" ")+c).split(" ");return!!D(c,b)}function S(){f.input=function(a){for(var b=0,c=a.length;b<c;b++)L[a[b]]=!!(a[b]in h);return L}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" "));f.inputtypes=function(a){for(var b=0,c,k=a.length;b<
k;b++){h.setAttribute("type",a[b]);if(c=h.type!=="text"){h.value=M;if(/^range$/.test(h.type)&&h.style.WebkitAppearance!==u){l.appendChild(h);c=e.defaultView;c=c.getComputedStyle&&c.getComputedStyle(h,null).WebkitAppearance!=="textfield"&&h.offsetHeight!==0;l.removeChild(h)}else/^(search|tel)$/.test(h.type)||(c=/^(url|email)$/.test(h.type)?h.checkValidity&&h.checkValidity()===false:h.value!=M)}N[a[b]]=!!c}return N}("search tel url email datetime date month week time datetime-local number range color".split(" "))}
var f={},l=e.documentElement,E=e.createElement("modernizr"),j=E.style,h=e.createElement("input"),M=":)",O=Object.prototype.toString,q=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),F="Webkit Moz O ms Khtml".split(" "),v={svg:"http://www.w3.org/2000/svg"},d={},N={},L={},P=[],w,Q=function(a){var b=document.createElement("style"),c=e.createElement("div");b.textContent=a+"{#modernizr{height:3px}}";(e.head||e.getElementsByTagName("head")[0]).appendChild(b);c.id="modernizr";l.appendChild(c);a=c.offsetHeight===
3;b.parentNode.removeChild(b);c.parentNode.removeChild(c);return!!a},o=function(){var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return function(b,c){c=c||document.createElement(a[b]||"div");b="on"+b;var k=b in c;if(!k){c.setAttribute||(c=document.createElement("div"));if(c.setAttribute&&c.removeAttribute){c.setAttribute(b,"");k=typeof c[b]=="function";if(typeof c[b]!="undefined")c[b]=u;c.removeAttribute(b)}}return k}}(),G={}.hasOwnProperty,R;R=
typeof G!=="undefined"&&typeof G.call!=="undefined"?function(a,b){return G.call(a,b)}:function(a,b){return b in a&&typeof a.constructor.prototype[b]==="undefined"};d.flexbox=function(){var a=e.createElement("div"),b=e.createElement("div");(function(k,g,r,x){g+=":";k.style.cssText=(g+q.join(r+";"+g)).slice(0,-g.length)+(x||"")})(a,"display","box","width:42px;padding:0;");b.style.cssText=q.join("box-flex:1;")+"width:10px;";a.appendChild(b);l.appendChild(a);var c=b.offsetWidth===42;a.removeChild(b);
l.removeChild(a);return c};d.canvas=function(){var a=e.createElement("canvas");return!!(a.getContext&&a.getContext("2d"))};d.canvastext=function(){return!!(f.canvas&&typeof e.createElement("canvas").getContext("2d").fillText=="function")};d.webgl=function(){var a=e.createElement("canvas");try{if(a.getContext("webgl"))return true}catch(b){}try{if(a.getContext("experimental-webgl"))return true}catch(c){}return false};d.touch=function(){return"ontouchstart"in i||Q("@media ("+q.join("touch-enabled),(")+
"modernizr)")};d.geolocation=function(){return!!navigator.geolocation};d.postmessage=function(){return!!i.postMessage};d.websqldatabase=function(){return!!i.openDatabase};d.indexedDB=function(){for(var a=-1,b=F.length;++a<b;){var c=F[a].toLowerCase();if(i[c+"_indexedDB"]||i[c+"IndexedDB"])return true}return false};d.hashchange=function(){return o("hashchange",i)&&(document.documentMode===u||document.documentMode>7)};d.history=function(){return!!(i.history&&history.pushState)};d.draganddrop=function(){return o("drag")&&
o("dragstart")&&o("dragenter")&&o("dragover")&&o("dragleave")&&o("dragend")&&o("drop")};d.websockets=function(){return"WebSocket"in i};d.rgba=function(){j.cssText="background-color:rgba(150,255,150,.5)";return s(j.backgroundColor,"rgba")};d.hsla=function(){j.cssText="background-color:hsla(120,40%,100%,.5)";return s(j.backgroundColor,"rgba")||s(j.backgroundColor,"hsla")};d.multiplebgs=function(){j.cssText="background:url(//:),url(//:),red url(//:)";return/(url\s*\(.*?){3}/.test(j.background)};d.backgroundsize=
function(){return n("backgroundSize")};d.borderimage=function(){return n("borderImage")};d.borderradius=function(){return n("borderRadius","",function(a){return s(a,"orderRadius")})};d.boxshadow=function(){return n("boxShadow")};d.textshadow=function(){return e.createElement("div").style.textShadow===""};d.opacity=function(){var a=q.join("opacity:.5;")+"";j.cssText=a;return s(j.opacity,"0.5")};d.cssanimations=function(){return n("animationName")};d.csscolumns=function(){return n("columnCount")};d.cssgradients=
function(){var a=("background-image:"+q.join("gradient(linear,left top,right bottom,from(#9f9),to(white));background-image:")+q.join("linear-gradient(left top,#9f9, white);background-image:")).slice(0,-17);j.cssText=a;return s(j.backgroundImage,"gradient")};d.cssreflections=function(){return n("boxReflect")};d.csstransforms=function(){return!!D(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])};d.csstransforms3d=function(){var a=!!D(["perspectiveProperty","WebkitPerspective",
"MozPerspective","OPerspective","msPerspective"]);if(a)a=Q("@media ("+q.join("transform-3d),(")+"modernizr)");return a};d.csstransitions=function(){return n("transitionProperty")};d.fontface=function(){var a,b=e.head||e.getElementsByTagName("head")[0]||l,c=e.createElement("style"),k=e.implementation||{hasFeature:function(){return false}};c.type="text/css";b.insertBefore(c,b.firstChild);a=c.sheet||c.styleSheet;b=k.hasFeature("CSS2","")?function(g){if(!(a&&g))return false;var r=false;try{a.insertRule(g,
0);r=!/unknown/i.test(a.cssRules[0].cssText);a.deleteRule(a.cssRules.length-1)}catch(x){}return r}:function(g){if(!(a&&g))return false;a.cssText=g;return a.cssText.length!==0&&!/unknown/i.test(a.cssText)&&a.cssText.replace(/\r+|\n+/g,"").indexOf(g.split(" ")[0])===0};f._fontfaceready=function(g){g(f.fontface)};return b('@font-face { font-family: "font"; src: "font.ttf"; }')};d.video=function(){var a=e.createElement("video"),b=!!a.canPlayType;if(b){b=new Boolean(b);b.ogg=a.canPlayType('video/ogg; codecs="theora"');
b.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"')||a.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');b.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}return b};d.audio=function(){var a=e.createElement("audio"),b=!!a.canPlayType;if(b){b=new Boolean(b);b.ogg=a.canPlayType('audio/ogg; codecs="vorbis"');b.mp3=a.canPlayType("audio/mpeg;");b.wav=a.canPlayType('audio/wav; codecs="1"');b.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")}return b};d.localstorage=function(){try{return"localStorage"in
i&&i.localStorage!==null}catch(a){return false}};d.sessionstorage=function(){try{return"sessionStorage"in i&&i.sessionStorage!==null}catch(a){return false}};d.webWorkers=function(){return!!i.Worker};d.applicationcache=function(){return!!i.applicationCache};d.svg=function(){return!!e.createElementNS&&!!e.createElementNS(v.svg,"svg").createSVGRect};d.inlinesvg=function(){var a=document.createElement("div");a.innerHTML="<svg/>";return(a.firstChild&&a.firstChild.namespaceURI)==v.svg};d.smil=function(){return!!e.createElementNS&&
/SVG/.test(O.call(e.createElementNS(v.svg,"animate")))};d.svgclippaths=function(){return!!e.createElementNS&&/SVG/.test(O.call(e.createElementNS(v.svg,"clipPath")))};for(var H in d)if(R(d,H)){w=H.toLowerCase();f[w]=d[H]();P.push((f[w]?"":"no-")+w)}f.input||S();f.crosswindowmessaging=f.postmessage;f.historymanagement=f.history;f.addTest=function(a,b){a=a.toLowerCase();if(!f[a]){b=!!b();l.className+=" "+(b?"":"no-")+a;f[a]=b;return f}};j.cssText="";E=h=null;i.attachEvent&&function(){var a=e.createElement("div");
a.innerHTML="<elem></elem>";return a.childNodes.length!==1}()&&function(a,b){function c(p){for(var m=-1;++m<r;)p.createElement(g[m])}function k(p,m){for(var I=p.length,t=-1,y,J=[];++t<I;){y=p[t];m=y.media||m;J.push(k(y.imports,m));J.push(y.cssText)}return J.join("")}var g="abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video".split("|"),r=g.length,x=RegExp("<(/*)(abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video)",
"gi"),T=RegExp("\\b(abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video)\\b(?!.*[;}])","gi"),z=b.createDocumentFragment(),A=b.documentElement,K=A.firstChild,B=b.createElement("style"),C=b.createElement("body");B.media="all";c(b);c(z);a.attachEvent("onbeforeprint",function(){for(var p=-1;++p<r;)for(var m=b.getElementsByTagName(g[p]),I=m.length,t=-1;++t<I;)if(m[t].className.indexOf("iepp_")<0)m[t].className+=" iepp_"+
g[p];K.insertBefore(B,K.firstChild);B.styleSheet.cssText=k(b.styleSheets,"all").replace(T,".iepp_$1");z.appendChild(b.body);A.appendChild(C);C.innerHTML=z.firstChild.innerHTML.replace(x,"<$1bdo")});a.attachEvent("onafterprint",function(){C.innerHTML="";A.removeChild(C);K.removeChild(B);A.appendChild(z.firstChild)})}(this,document);f._enableHTML5=true;f._version="1.6";l.className=l.className.replace(/\bno-js\b/,"")+" js";l.className+=" "+P.join(" ");return f}(this,this.document);

View file

@ -1,3 +0,0 @@
*
!.gitignore

View file

@ -1,34 +0,0 @@
(function($){
})(this.jQuery);
window.log = function(){
log.history = log.history || [];
log.history.push(arguments);
if(this.console){
console.log( Array.prototype.slice.call(arguments) );
}
};
(function(doc){
var write = doc.write;
doc.write = function(q){
log('document.write(): ',arguments);
if (/docwriteregexwhitelist/.test(q)) write.apply(doc,arguments);
};
})(document);

View file

@ -1,59 +0,0 @@
// call PROFILE.show() to show the profileViewer
var PROFILE = {
init : function(bool) {
// define what objects, constructors and functions you want to profile
// documentation here: http://developer.yahoo.com/yui/profiler/
YAHOO.tool.Profiler.registerObject("jQuery", jQuery, true);
// the following would profile all methods within constructor's prototype
// YAHOO.tool.Profiler.registerConstructor("Person");
// the following would profile the global function sayHi
// YAHOO.tool.Profiler.registerFunction("sayHi", window);
// if true is passed into init(), F9 will bring up the profiler
if (bool){
$(document).keyup(function(e){
if (e.keyCode === 120){
PROFILE.show();
$(document).unbind('keyup',arguments.callee);
}
})
}
},
//When the showProfile button is clicked, use YUI Loader to get all required
//dependencies and then show the profile:
show : function() {
var s = document.createElement('link');
s.setAttribute('rel','stylesheet');
s.setAttribute('type','text/css');
s.setAttribute('href','js/profiling/yahoo-profiling.css');
document.body.appendChild(s);
YAHOO.util.Dom.addClass(document.body, 'yui-skin-sam');
//instantiate ProfilerViewer with desired options:
var pv = new YAHOO.widget.ProfilerViewer("", {
visible: true, //expand the viewer mmediately after instantiation
showChart: true,
// base:"../../build/",
swfUrl: "js/profiling/charts.swf"
});
}
};
// check some global debug variable to see if we should be profiling..
if (true) { PROFILE.init(true) }

View file

@ -1,7 +0,0 @@
/*
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 2.7.0
*/
.yui-skin-sam .yui-pv{background-color:#4a4a4a;font:arial;position:relative;width:99%;z-index:1000;margin-bottom:1em;overflow:hidden;}.yui-skin-sam .yui-pv .hd{background:url(http://yui.yahooapis.com/2.7.0/build/profilerviewer/assets/skins/sam/header_background.png) repeat-x;min-height:30px;overflow:hidden;zoom:1;padding:2px 0;}.yui-skin-sam .yui-pv .hd h4{padding:8px 10px;margin:0;font:bold 14px arial;color:#fff;}.yui-skin-sam .yui-pv .hd a{background:#3f6bc3;font:bold 11px arial;color:#fff;padding:4px;margin:3px 10px 0 0;border:1px solid #3f567d;cursor:pointer;display:block;float:right;}.yui-skin-sam .yui-pv .hd span{display:none;}.yui-skin-sam .yui-pv .hd span.yui-pv-busy{height:18px;width:18px;background:url(http://yui.yahooapis.com/2.7.0/build/profilerviewer/assets/skins/sam/wait.gif) no-repeat;overflow:hidden;display:block;float:right;margin:4px 10px 0 0;}.yui-skin-sam .yui-pv .hd:after,.yui-pv .bd:after,.yui-skin-sam .yui-pv-chartlegend dl:after{content:'.';visibility:hidden;clear:left;height:0;display:block;}.yui-skin-sam .yui-pv .bd{position:relative;zoom:1;overflow-x:auto;overflow-y:hidden;}.yui-skin-sam .yui-pv .yui-pv-table{padding:0 10px;margin:5px 0 10px 0;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-bd td{color:#eeee5c;font:12px arial;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd{background:#929292;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even{background:#58637a;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even td.yui-dt-desc{background:#384970;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd td.yui-dt-desc{background:#6F6E6E;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th{background-image:none;background:#2E2D2D;}.yui-skin-sam .yui-pv th.yui-dt-asc .yui-dt-liner{background:transparent url(http://yui.yahooapis.com/2.7.0/build/profilerviewer/assets/skins/sam/asc.gif) no-repeat scroll right center;}.yui-skin-sam .yui-pv th.yui-dt-desc .yui-dt-liner{background:transparent url(http://yui.yahooapis.com/2.7.0/build/profilerviewer/assets/skins/sam/desc.gif) no-repeat scroll right center;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th a{color:#fff;font:bold 12px arial;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th.yui-dt-desc{background:#333;}.yui-skin-sam .yui-pv-chartcontainer{padding:0 10px;}.yui-skin-sam .yui-pv-chart{height:250px;clear:right;margin:5px 0 0 0;color:#fff;}.yui-skin-sam .yui-pv-chartlegend div{float:right;margin:0 0 0 10px;_width:250px;}.yui-skin-sam .yui-pv-chartlegend dl{border:1px solid #999;padding:.2em 0 .2em .5em;zoom:1;margin:5px 0;}.yui-skin-sam .yui-pv-chartlegend dt{float:left;display:block;height:.7em;width:.7em;padding:0;}.yui-skin-sam .yui-pv-chartlegend dd{float:left;display:block;color:#fff;margin:0 1em 0 .5em;padding:0;font:11px arial;}.yui-skin-sam .yui-pv-minimized{height:35px;}.yui-skin-sam .yui-pv-minimized .bd{top:-3000px;}.yui-skin-sam .yui-pv-minimized .hd a.yui-pv-refresh{display:none;}

File diff suppressed because one or more lines are too long

View file

@ -1,26 +0,0 @@
/* Author:
*/

View file

@ -1,5 +0,0 @@
# www.robotstxt.org/
# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
User-agent: *

21
docs/storage/aws.markdown Normal file
View file

@ -0,0 +1,21 @@
---
layout: default
title: Storage
---
## AWS Specific Options
Here's a couple Fog features specific only to AWS S3.
**Encryption**. Amazon provides the option to AES256 encrypt files at rest on
upload by setting the "x-amz-server-side-encryption" HTTP request header to
AES256. You can short hand set this HTTP header via the ````encryption```` key
value pair. For example,
# encrypt file at rest
file = directory.files.create(
:key => 'resume.html',
:body => File.open("/path/to/my/resume.html"),
:public => true,
:encryption => 'AES256'
)

View file

@ -1,204 +0,0 @@
---
layout: default
title: Storage
---
Having Ruby experience makes you hirable; but how can you stand out? You need to demonstrate your abilities. What better way than using Ruby and "the cloud" to store and serve your resume!
In this blog post you will learn to use <a href="http://github.com/geemus/fog">fog</a> - the cloud computing library - to upload your resume to Amazon's <a href="http://aws.amazon.com/s3/">Simple Storage Service</a> (S3), Rackspace's <a href="http://www.rackspacecloud.com/cloud_hosting_products/files">CloudFiles</a> or Google's <a href="http://code.google.com/apis/storage/">Storage for Developers</a>.
Here's my out of date resume stored on <a href="http://geemus.s3.amazonaws.com/resume.html">S3</a>, <a href="http://c0023559.cdn2.cloudfiles.rackspacecloud.com/resume.html">CloudFiles</a> and <a href="https://geemus.commondatastorage.googleapis.com/resume.html">Google Storage</a>; programmatically stored in the cloud using this tutorial. NOTE: my boss would like me to add that I'm not currently looking for a new gig ;)
Check out those cloud-specific URLs! You could put all three in your job application, add the Ruby source for how you did it, and have your choice of Ruby jobs for being so awesome!
How? The all-clouds-in-one library of choice is <a href="https://github.com/geemus/fog">fog</a>.
## Installing fog
fog is distributed as a RubyGem:
gem install fog
Or add it in your application's Gemfile:
gem "fog"
## Using Amazon S3 and fog
Sign up for an account <a href="http://aws-portal.amazon.com/gp/aws/developer/subscription/index.html?productCode=AmazonS3">here</a> and copy down your secret access key and access key id from <a href="http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key">here</a>. We are about to get into the code samples, so be sure to fill in anything in ALL_CAPS with your own values!
First, create a connection with your new account:
require 'rubygems'
require 'fog'
# create a connection
connection = Fog::Storage.new({
:provider => 'AWS',
:aws_secret_access_key => YOUR_SECRET_ACCESS_KEY,
:aws_access_key_id => YOUR_SECRET_ACCESS_KEY_ID
})
# First, a place to contain the glorious details
directory = connection.directories.create(
:key => "fog-demo-#{Time.now.to_i}", # globally unique name
:public => true
)
# list directories
p connection.directories
# upload that resume
file = directory.files.create(
:key => 'resume.html',
:body => File.open("/path/to/my/resume.html"),
:public => true
)
If you are anything like me, you will continually tweak your resume. Pushing updates is easy:
file.body = File.open("/path/to/my/resume.html")
file.save
As you can see, cloud storage files in fog are a lot like an ActiveRecord model. Attributes that can be changed and a `#save` method that creates or updates the stored file in the cloud.
But if it took you longer to realize the mistake you might not still have file around, but you've got options.
directory = connection.directories.get("proclamations1234567890")
# get the resume file
file = directory.files.get('resume.html')
file.body = File.open("/path/to/my/resume.html")
file.save
# also, create(attributes) is just new(attributes).save, so you can also do:
file = directory.files.new({
:key => 'resume.html',
:body => 'improvements',
:public => true
})
file.save
## Backing up your files
Now you've got a bunch of files in S3: your resume, some code samples,
and maybe some pictures of your cat doing funny stuff. Since this is
all of vital importance, you need to back it up.
# copy each file to local disk
directory.files.each do |s3_file|
File.open(s3_file.key, 'w') do |local_file|
local_file.write(s3_file.body)
end
end
One caveat: it's way more efficient to do this:
# do two things per file
directory.files.each do |file|
do_one_thing(file)
do_another_thing(file)
end
than it is to do this:
# do two things per file
directory.files.each do |file|
do_one_thing(file)
end.each do |file|
do_another_thing(file)
end
The reason is that the list of files might be large. Really
large. Eat-all-your-RAM-and-ask-for-more large. Therefore, every time
you say `files.each`, fog makes a fresh set of API calls to Amazon to
list the available files (Amazon's API returns a page at a time, so
fog works a page at a time in order to keep its memory requirements sane).
## Sending it out
Alright, so you (eventually) become satisfied enough to send it off, what is the URL endpoint to your resume?
puts file.public_url
Pop that link in an email and you should be ready to cruise job ads and send your resume far and wide (Engine Yard is <a href="http://www.engineyard.com/company/careers/wanted-head-in-the-clouds-engineer">hiring</a>, so check us out!). Now you are set, unless you are interviewing for Google, or Rackspace... Both of these companies have their own cloud storage services, so using Amazon S3 might not be the foot in the door you hoped for.
More clouds? How much extra stuff will you have to do for these services!?! Hardly anything needs to change, you just have to pass slightly different credentials in, but I'm getting ahead of myself.
## Google Storage for Developers
Sign up <a href="http://gs-signup-redirect.appspot.com/">here</a> and get your credentials <a href="https://sandbox.google.com/storage/m/">here</a>.
connection = Fog::Storage.new({
:provider => 'Google',
:google_storage_secret_access_key => YOUR_SECRET_ACCESS_KEY,
:google_storage_access_key_id => YOUR_SECRET_ACCESS_KEY_ID
})
## Rackspace CloudFiles
Rackspace has <a href="http://www.rackspacecloud.com/cloud_hosting_products/files">Cloud Files</a> and you can sign up <a href="https://www.rackspacecloud.com/signup">here</a> and get your credentials <a href="https://manage.rackspacecloud.com/APIAccess.do">here</a>.
connection = Fog::Storage.new({
:provider => 'Rackspace',
:rackspace_username => RACKSPACE_USERNAME,
:rackspace_api_key => RACKSPACE_API_KEY
})
If you work with the European cloud from Rackspace you have to add the following:
:rackspace_auth_url => "lon.auth.api.rackspacecloud.com"
Then create, save, destroy as per fog-for-AWS. The `:public => true` option when creating directories (see above) is important for Rackspace; your folder and files won't be shared to Rackspace's CDN and hence your users without it. Similarly the `:public =&gt; true` on files is important for AWS and Google or they will be private.
## Local Storage
While you are working out the kinks you might not want to do everything live though, ditto for while you are running tests, so you have a couple options to try before you buy. First, you can use a local provider to store things in a directory on your machine.
connection = Fog::Storage.new({
:provider => 'Local',
:local_root => '~/fog'
})
## Mocking out Cloud Storage
Of course when you are testing or developing you can always just use the mocks (at least for AWS and Google, Rackspace still needs mocks implemented if you are looking for somewhere to contribute). They emulate the behavior of the external systems without actually using them. It is as simple as:
Fog.mock!
connection = Fog::Storage.new(config_hash)
## Cleaning up
Fog takes care of the rest so you can focus on your cover letter. And with the awesome cover letter and cloud delivered resume you are probably a shoe-in. So all that is left is to cleanup that leftover job hunt residue.
file.destroy
directory.destroy
## Checking if a file already exists
Sometimes you might want to find out some information about a file without retrieving the whole file. You can do that using 'head'.
#returns nil if the file doesn't exist
unless directory.files.head('resume.html')
#do something, like creating the file
end
#returns a hash with the following data:
# 'key' - Key for the object
# 'Content-Length' - Size of object contents
# 'Content-Type' - MIME type of object
# 'ETag' - Etag of object
# 'Last-Modified' - Last modified timestamp for object
puts directory.files.head('resume.html')
## Summary
All done. Try out all the different options and let me know if you have any bugs or issues. I also wrote up a more <a href="https://gist.github.com/710869">consolidated example as a script</a> that you can use for reference.
Bonus, note the `Fog.mock!` command. In your tests you can easily mock out calls to cloud providers.
Please let me know in the comments if you got a new Ruby job because you hosted your CV on 3 different Cloud Stores without getting your hands dirty.
Have questions or comments? Hop into <a href="irc://irc.freenode.net/">#ruby-fog</a> on freenode, ping <a href="http://twitter.com/fog">@fog</a> or <a href="http://twitter.com/geemus">@geemus</a>.
And please always remember that I accept high fives and contributions!

View file

@ -1,71 +0,0 @@
require 'rubygems'
require 'shindo'
require File.join(File.dirname(__FILE__), '..', 'lib', 'fog')
require File.join(File.dirname(__FILE__), '..', 'tests', 'helper')
Shindo.tests('compute examples', 'compute') do
# iterate over all the providers
Fog.providers.each do |provider|
# FIXME: implement expected shared compute stuff for these providers as well
next if ['Bluebox', 'Brightbox', 'Ecloud', 'GoGrid', 'Linode', 'NewServers', 'Ninefold', 'Slicehost', 'StormOnDemand', 'VirtualBox', 'Voxel'].include?(provider)
provider = eval(provider) # convert from string to object
# skip if provider does not have compute
next unless provider.respond_to?(:services) && provider.services.include?(:compute)
tests(provider, provider.to_s.downcase) do
# use shortcuts to instantiate connection
@compute = Fog::Compute.new(:provider => provider.to_s)
# create a server
tests('@server = @compute.servers.bootstrap').succeeds do
@server = @compute.servers.bootstrap
end
# list servers
tests('@servers = @compute.servers').succeeds do
@servers = @compute.servers
end
# get a server
tests('@compute.servers.get(@server.identity)').succeeds do
@compute.servers.get(@server.identity)
end
# ssh to a server
tests('@server.ssh("pwd")').succeeds do
@server.ssh('pwd')
end
# scp a file to a server
lorem_path = File.join([File.dirname(__FILE__), '..', 'tests', 'lorem.txt'])
tests("@server.scp('#{lorem_path}', 'lorem.txt')").succeeds do
@server.scp(lorem_path, 'lorem.txt')
end
# scp a directory to a server
Dir.mkdir('/tmp/lorem')
file = ::File.new('/tmp/lorem/lorem.txt', 'w')
file.write(File.read(lorem_path))
lorem_dir = File.join([File.dirname(__FILE__), '..', 'tests'])
tests("@server.scp('#{lorem_dir}', '/tmp/lorem', :recursive => true)").succeeds do
@server.scp(lorem_dir, '/tmp/lorem', :recursive => true)
end
File.delete('/tmp/lorem/lorem.txt')
Dir.rmdir('/tmp/lorem')
# destroy the server
tests('@server.destroy').succeeds do
@server.destroy
end
end
end
end

View file

@ -1,78 +0,0 @@
require 'rubygems'
require 'shindo'
require File.join(File.dirname(__FILE__), '..', 'lib', 'fog')
require File.join(File.dirname(__FILE__), '..', 'tests', 'helper')
Shindo.tests('dns examples', 'dns') do
# iterate over all the providers
Fog.providers.each do |provider|
provider = eval(provider) # convert from string to object
# skip if provider does not have storage
next unless provider.respond_to?(:services) && provider.services.include?(:dns)
tests(provider, provider.to_s.downcase) do
# use shortcuts to instantiate connection
@dns = Fog::DNS.new(:provider => provider.to_s)
# create a zone
# domain should be the hostname
# email is only required for linode, but included for consistency
tests('@zone = @dns.zones.create').succeeds do
@zone = @dns.zones.create(
:domain => 'fogdnsexamples.com',
:email => 'tests@fogdnsexamples.com'
)
end
# create a record in the zone
# ip is the address to route to
# name is the name for the record
# type is the type of record to create
tests('@record = @zone.records.create').succeeds do
@record = @zone.records.create(
:value => '1.2.3.4',
:name => 'www.fogdnsexamples.com',
:type => 'A'
)
end
# list zones
tests('@zones = @dns.zones').succeeds do
@zones = @dns.zones
end
# get a zone
tests('@dns.zones.get(@zone.identity)').succeeds do
@dns.zones.get(@zone.identity)
end
# list records
tests('@records = @zone.records').succeeds do
@records = @zone.records
end
# get a record
tests('@zone.records.get(@record.identity)').succeeds do
@zone.records.get(@record.identity)
end
# destroy the record
tests('@record.destroy').succeeds do
@record.destroy
end
# destroy the zone
tests('@zone.destroy').succeeds do
@zone.destroy
end
end
end
end

View file

@ -1,105 +0,0 @@
require 'rubygems'
require 'shindo'
require File.join(File.dirname(__FILE__), '..', 'lib', 'fog')
require File.join(File.dirname(__FILE__), '..', 'tests', 'helper')
Shindo.tests('storage examples', 'storage') do
# iterate over all the providers
Fog.providers.each do |provider|
provider = eval(provider) # convert from string to object
# skip if provider does not have storage
next unless provider.respond_to?(:services) && provider.services.include?(:storage)
tests(provider, provider.to_s.downcase) do
# use shortcuts to instantiate connection
@storage = Fog::Storage.new(:provider => provider.to_s)
# for compatibility public is simply true or false
[false, true].each do |publicity|
tests(":public => #{publicity}") do
# create a directory
# key should be a unique string
# public should be a boolean
tests('@directory = @storage.directories.create').succeeds do
@directory = @storage.directories.create(
:key => "fogstoragedirectory#{Time.now.to_i}",
:public => publicity
)
end
# list directories
tests('@directories = @storage.directories').succeeds do
@directories = @storage.directories
end
# get a directory
tests('@storage.directories.get(@directory.identity)').succeeds do
@storage.directories.get(@directory.identity)
end
# create a file in the directory
# key can be any string
# body can be a string or a file as File.open(path)
# public should be a boolean and match the directory
tests('@file = @directory.files.create').succeeds do
@file = @directory.files.create(
:body => 'fog_storage_object_body',
:key => 'fogstorageobject',
:public => publicity
)
end
# list files
tests('@files = @directory.files').succeeds do
@files = @directory.files
end
# get a file
tests('@directory.files.get(@file.identity)').succeeds do
@directory.files.get(@file.identity)
end
# test the publicity of files
# Local is unable to inherently serve files, so we can skip it
unless provider == Local
# if the file is public it should have a url
test('!!@file.public_url == publicity') do
pending if Fog.mocking?
!!@file.public_url == publicity
end
# if it is public ensure that public url is usable
if publicity
tests('Excon.get(@file.public_url).body').returns('fog_storage_object_body') do
pending if Fog.mocking?
Excon.get(@file.public_url).body
end
end
end
# destroy the file
tests('@file.destroy').succeeds do
@file.destroy
end
# destroy the directory
tests('@directory.destroy').succeeds do
@directory.destroy
end
end
end
end
end
end

View file

@ -6,10 +6,11 @@ Gem::Specification.new do |s|
## If your rubyforge_project name is different, then edit it and comment out
## the sub! line in the Rakefile
s.name = 'hpfog'
s.version = '0.0.18' # fog base 1.0.0
s.date = '2012-12-04'
s.version = '0.0.19' # fog base 1.8.0
s.date = '2013-01-18'
#s.rubyforge_project = 'fog'
## Make sure your summary is short. The description may be as long
## as you like.
s.summary = "brings clouds to you"
@ -37,12 +38,12 @@ Gem::Specification.new do |s|
## Specify any RDoc options here. You'll want to add your README and
## LICENSE files to the extra_rdoc_files list.
s.rdoc_options = ["--charset=UTF-8"]
s.extra_rdoc_files = %w[README.rdoc]
s.extra_rdoc_files = %w[README.md]
## List your runtime dependencies here. Runtime dependencies are those
## that are needed for an end user to actually USE your code.
s.add_dependency('builder')
s.add_dependency('excon', '~>0.14.0')
s.add_dependency('excon', '~>0.14')
s.add_dependency('formatador', '~>0.2.0')
s.add_dependency('multi_json', '~>1.0')
s.add_dependency('mime-types')
@ -55,10 +56,15 @@ Gem::Specification.new do |s|
## those that are only needed during development
s.add_development_dependency('jekyll')
s.add_development_dependency('rake')
s.add_development_dependency('rdoc')
s.add_development_dependency('rbvmomi')
s.add_development_dependency('yard')
s.add_development_dependency('thor')
s.add_development_dependency('rspec', '~>1.3.1')
s.add_development_dependency('rbovirt', '>=0.0.11')
s.add_development_dependency('shindo', '~>0.3.4')
s.add_development_dependency('virtualbox', '~>0.9.1')
s.add_development_dependency('fission')
s.add_development_dependency('pry')
# s.add_development_dependency('ruby-libvirt','~>0.4.0')
s.files = `git ls-files`.split("\n")

View file

@ -1,11 +1,6 @@
require File.join(File.dirname(__FILE__), 'fog', 'core')
module Fog
unless const_defined?(:VERSION)
VERSION = '1.0.0'
end
end
# FIXME: these should go away (force usage of Fog::[Compute, CDN, DNS, Storage] etc)
@ -13,6 +8,10 @@ require 'fog/providers'
require 'fog/terremark'
require 'fog/compute'
require 'fog/identity'
require 'fog/image'
require 'fog/volume'
require 'fog/cdn'
require 'fog/dns'
require 'fog/network'
require 'fog/storage'

11
lib/fog/atmos.rb Normal file
View file

@ -0,0 +1,11 @@
require 'fog/core'
module Fog
module Atmos
extend Fog::Provider
service(:storage, 'atmos/storage', 'Storage')
end
end

View file

@ -1,20 +1,20 @@
require 'fog/core/collection'
require 'fog/ninefold/models/storage/directory'
require 'fog/atmos/models/storage/directory'
module Fog
module Storage
class Ninefold
class Atmos
class Directories < Fog::Collection
attribute :directory
model Fog::Storage::Ninefold::Directory
model Fog::Storage::Atmos::Directory
def all
directory ? ns = directory.key : ns = ''
ns = ns + '/' unless ns =~ /\/$/
data = connection.get_namespace(ns).body[:DirectoryList]
data = service.get_namespace(ns).body[:DirectoryList]
data = {:DirectoryEntry => []} if data.kind_of? String
data[:DirectoryEntry] = [data[:DirectoryEntry]] if data[:DirectoryEntry].kind_of? Hash
dirs = data[:DirectoryEntry].select {|de| de[:FileType] == 'directory'}
@ -28,11 +28,11 @@ module Fog
def get(key, options = {})
return nil if key == '' # Root dir shouldn't be retrieved like this.
key =~ /\/$/ ? ns = key : ns = key + '/'
res = connection.get_namespace ns
res = service.get_namespace ns
emc_meta = res.headers['x-emc-meta']
obj_id = emc_meta.scan(/objectid=(\w+),/).flatten[0]
new(:objectid => obj_id, :key => ns)
rescue Fog::Storage::Ninefold::NotFound
rescue Fog::Storage::Atmos::NotFound
nil
end

View file

@ -2,7 +2,7 @@ require 'fog/core/model'
module Fog
module Storage
class Ninefold
class Atmos
class Directory < Fog::Model
@ -11,26 +11,26 @@ module Fog
def files
@files ||= begin
Fog::Storage::Ninefold::Files.new(
:directory => self,
:connection => connection
)
end
Fog::Storage::Atmos::Files.new(
:directory => self,
:service => service
)
end
end
def directories
@directories ||= begin
Fog::Storage::Ninefold::Directories.new(
:directory => self,
:connection => connection
)
end
Fog::Storage::Atmos::Directories.new(
:directory => self,
:service => service
)
end
end
def save
self.key = attributes[:directory].key + key if attributes[:directory]
self.key = key + '/' unless key =~ /\/$/
res = connection.post_namespace key
res = service.post_namespace key
reload
end
@ -42,7 +42,7 @@ module Fog
d.destroy(opts)
end
end
connection.delete_namespace key
service.delete_namespace key
end

View file

@ -2,7 +2,7 @@ require 'fog/core/model'
module Fog
module Storage
class Ninefold
class Atmos
class File < Fog::Model
@ -13,7 +13,7 @@ module Fog
attribute :objectid, :aliases => :ObjectID
def body
attributes[:body] ||= if last_modified
attributes[:body] ||= if objectid
collection.get(identity).body
else
''
@ -28,9 +28,17 @@ module Fog
@directory
end
def copy(target_directory_key, target_file_key, options={})
target_directory = service.directories.new(:key => target_directory_key)
target_directory.files.create(
:key => target_file_key,
:body => body
)
end
def destroy
requires :directory, :key
connection.delete_namespace([directory.key, key].join('/'))
service.delete_namespace([directory.key, key].join('/'))
true
end
@ -49,21 +57,22 @@ module Fog
# By default, expire in 5 years
def public_url(expires = (Time.now + 5 * 365 * 24 * 60 * 60))
requires :objectid
# TODO - more efficient method to get this?
storage = Fog::Storage.new(:provider => 'Ninefold')
uri = URI::HTTP.build(:scheme => Fog::Storage::Ninefold::STORAGE_SCHEME, :host => Fog::Storage::Ninefold::STORAGE_HOST, :port => Fog::Storage::Ninefold::STORAGE_PORT.to_i, :path => "/rest/objects/#{objectid}" )
Fog::Storage.new(:provider => 'Ninefold').uid
file = directory.files.head(key)
self.objectid = if file.present? then file.attributes['x-emc-meta'].scan(/objectid=(\w+),/).flatten[0] else nil end
if self.objectid.present?
uri = URI::HTTP.build(:scheme => service.ssl? ? "http" : "https" , :host => service.host, :port => service.port.to_i, :path => "/rest/objects/#{self.objectid}" )
sb = "GET\n"
sb += uri.path.downcase + "\n"
sb += service.uid + "\n"
sb += String(expires.to_i())
sb = "GET\n"
sb += uri.path.downcase + "\n"
sb += storage.uid + "\n"
sb += String(expires.to_i())
signature = storage.sign( sb )
uri.query = "uid=#{CGI::escape(storage.uid)}&expires=#{expires.to_i()}&signature=#{CGI::escape(signature)}"
uri.to_s
signature = service.sign( sb )
uri.query = "uid=#{CGI::escape(service.uid)}&expires=#{expires.to_i()}&signature=#{CGI::escape(signature)}"
uri.to_s
else
nil
end
end
def save(options = {})
@ -73,13 +82,15 @@ module Fog
options[:headers] ||= {}
options[:headers]['Content-Type'] = content_type if content_type
options[:body] = body
if objectid
# pre-existing file, do a PUT
data = connection.put_namespace(ns, options)
else
# new file, POST
data = connection.post_namespace(ns, options)
begin
data = service.post_namespace(ns, options)
self.objectid = data.headers['location'].split('/')[-1]
rescue => error
if error.message =~ /The resource you are trying to create already exists./
data = service.put_namespace(ns, options)
else
raise error
end
end
# merge_attributes(data.headers)
true

View file

@ -1,9 +1,9 @@
require 'fog/core/collection'
require 'fog/ninefold/models/storage/file'
require 'fog/atmos/models/storage/file'
module Fog
module Storage
class Ninefold
class Atmos
class Files < Fog::Collection
@ -13,13 +13,13 @@ module Fog
attribute :path
attribute :prefix
model Fog::Storage::Ninefold::File
model Fog::Storage::Atmos::File
def all(options = {})
requires :directory
directory ? ns = directory.key : ns = ''
ns = ns + '/' unless ns =~ /\/$/
data = connection.get_namespace(ns).body[:DirectoryList]
data = service.get_namespace(ns).body[:DirectoryList]
data = {:DirectoryEntry => []} if data.kind_of? String
data[:DirectoryEntry] = [data[:DirectoryEntry]] if data[:DirectoryEntry].kind_of? Hash
files = data[:DirectoryEntry].select {|de| de[:FileType] == 'regular'}
@ -32,13 +32,13 @@ module Fog
def get(key, &block)
requires :directory
data = connection.get_namespace(directory.key + key, :parse => false)#, &block)
data = service.get_namespace(directory.key + key, :parse => false)#, &block)
file_data = data.headers.merge({
:body => data.body,
:key => key
})
new(file_data)
rescue Fog::Storage::Ninefold::NotFound
rescue Fog::Storage::Atmos::NotFound
nil
end
@ -51,12 +51,13 @@ module Fog
def head(key, options = {})
requires :directory
data = connection.head_object(directory.key, key)
data = service.head_namespace(directory.key + key, :parse => false)
file_data = data.headers.merge({
:body => data.body,
:key => key
})
new(file_data)
rescue Fog::Storage::Rackspace::NotFound
rescue Fog::Storage::Atmos::NotFound
nil
end

View file

@ -1,6 +1,6 @@
module Fog
module Storage
class Ninefold
class Atmos
class Real
def delete_namespace(namespace = '', options = {})

View file

@ -1,6 +1,6 @@
module Fog
module Storage
class Ninefold
class Atmos
class Real
def get_namespace(namespace = '', options = {})

View file

@ -0,0 +1,20 @@
module Fog
module Storage
class Atmos
class Real
def head_namespace(namespace = '', options = {})
options = options.reject {|key, value| value.nil?}
request({
:expects => 200,
:method => 'HEAD',
:path => "namespace/" + namespace,
:query => {},
:parse => true
}.merge(options))
end
end
end
end
end

View file

@ -1,6 +1,6 @@
module Fog
module Storage
class Ninefold
class Atmos
class Real
def post_namespace(namespace = '', options = {})

View file

@ -1,6 +1,6 @@
module Fog
module Storage
class Ninefold
class Atmos
class Real
def put_namespace(namespace = '', options = {})

186
lib/fog/atmos/storage.rb Normal file
View file

@ -0,0 +1,186 @@
require 'fog/atmos'
require 'fog/storage'
module Fog
module Storage
class Atmos < Fog::Service
requires :atmos_storage_endpoint,
:atmos_storage_secret,
:atmos_storage_token
recognizes :persistent
model_path 'fog/atmos/models/storage'
model :directory
collection :directories
model :file
collection :files
request_path 'fog/atmos/requests/storage'
# request :delete_container
request :get_namespace
request :head_namespace
request :post_namespace
request :put_namespace
request :delete_namespace
module Utils
ENDPOINT_REGEX = /(https*):\/\/([a-zA-Z0-9\.\-]+)(:[0-9]+)?(\/.*)?/
def ssl?
protocol = @endpoint.match(ENDPOINT_REGEX)[1]
raise ArgumentError, 'Invalid endpoint URL' if protocol.nil?
return true if protocol == 'https'
return false if protocol == 'http'
raise ArgumentError, "Unknown protocol #{protocol}"
end
def port
port = @endpoint.match(ENDPOINT_REGEX)[3]
return ssl? ? 443 : 80 if port.nil?
port.split(':')[1].to_i
end
def host
@endpoint.match(ENDPOINT_REGEX)[2]
end
def api_path
@endpoint.match(ENDPOINT_REGEX)[4]
end
def setup_credentials(options)
@storage_token = options[:atmos_storage_token]
@storage_secret = options[:atmos_storage_secret]
@storage_secret_decoded = Base64.decode64(@storage_secret)
@endpoint = options[:atmos_storage_endpoint]
@prefix = self.ssl? ? 'https' : 'http'
@storage_host = self.host
@storage_port = self.port
@api_path = self.api_path
end
end
class Mock
include Utils
def initialize(options={})
require 'mime/types'
setup_credentials(options)
end
def request(options)
raise "Atmos Storage mocks not implemented"
end
end
class Real
include Utils
def initialize(options={})
require 'mime/types'
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@hmac = Fog::HMAC.new('sha1', @storage_secret_decoded)
@persistent = options.fetch(:persistent, false)
@connection = Fog::Connection.new("#{@prefix}://#{@storage_host}:#{@storage_port}",
@persistent, @connection_options)
end
def uid
@storage_token#.split('/')[-1]
end
def sign(string)
value = @hmac.sign(string)
Base64.encode64( value ).chomp()
end
def reload
@connection.reset
end
def request(params, &block)
req_path = params[:path]
# Force set host and port
params.merge!({
:host => @storage_host,
:path => "#{@api_path}/rest/#{params[:path]}",
})
# Set default method and headers
params = {:method => 'GET', :headers => {}}.merge params
params[:headers]["Content-Type"] ||= "application/octet-stream"
# Add request date
params[:headers]["date"] = Time.now().httpdate()
params[:headers]["x-emc-uid"] = @storage_token
# Build signature string
signstring = ""
signstring += params[:method]
signstring += "\n"
signstring += params[:headers]["Content-Type"]
signstring += "\n"
if( params[:headers]["range"] )
signstring += params[:headers]["range"]
end
signstring += "\n"
signstring += params[:headers]["date"]
signstring += "\n"
signstring += "/rest/" + URI.unescape( req_path ).downcase
query_str = params[:query].map{|k,v| "#{k}=#{v}"}.join('&')
signstring += '?' + query_str unless query_str.empty?
signstring += "\n"
customheaders = {}
params[:headers].each { |key,value|
case key
when 'x-emc-date', 'x-emc-signature'
#skip
when /^x-emc-/
customheaders[ key.downcase ] = value
end
}
header_arr = customheaders.sort()
header_arr.each { |key,value|
# Values are lowercase and whitespace-normalized
signstring += key + ":" + value.strip.chomp.squeeze( " " ) + "\n"
}
digest = @hmac.sign(signstring.chomp())
signature = Base64.encode64( digest ).chomp()
params[:headers]["x-emc-signature"] = signature
begin
response = @connection.request(params, &block)
rescue Excon::Errors::HTTPStatusError => error
raise case error
when Excon::Errors::NotFound
Fog::Storage::Atmos::NotFound.slurp(error)
else
error
end
end
unless response.body.empty?
if params[:parse]
document = Fog::ToHashDocument.new
parser = Nokogiri::XML::SAX::PushParser.new(document)
parser << response.body
parser.finish
response.body = document.body
end
end
response
end
end
end
end
end

View file

@ -1,24 +1,31 @@
require(File.expand_path(File.join(File.dirname(__FILE__), 'core')))
require 'fog/core'
require 'fog/aws/credential_fetcher'
require 'fog/aws/signaturev4'
module Fog
module AWS
COMPLIANT_BUCKET_NAMES = /^(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}$))(?:[a-z0-9]|\-(?![\.])){1,61}[a-z0-9]$/
extend Fog::Provider
service(:auto_scaling, 'aws/auto_scaling', 'AutoScaling')
service(:beanstalk, 'aws/beanstalk', 'ElasticBeanstalk')
service(:cdn, 'aws/cdn', 'CDN')
service(:compute, 'aws/compute', 'Compute')
service(:cloud_formation, 'aws/cloud_formation', 'CloudFormation')
service(:cloud_watch, 'aws/cloud_watch', 'CloudWatch')
service(:dynamodb, 'aws/dynamodb', 'DynamoDB')
service(:dns, 'aws/dns', 'DNS')
service(:elasticache, 'aws/elasticache', 'Elasticache')
service(:elb, 'aws/elb', 'ELB')
service(:emr, 'aws/emr', 'EMR')
service(:glacier, 'aws/glacier', 'Glacier')
service(:iam, 'aws/iam', 'IAM')
service(:rds, 'aws/rds', 'RDS')
service(:ses, 'aws/ses', 'SES')
service(:simpledb, 'aws/simpledb', 'SimpleDB')
service(:sns, 'aws/sns', 'SNS')
service(:sqs, 'aws/sqs', 'SQS')
service(:sts, 'aws/sts', 'STS')
service(:storage, 'aws/storage', 'Storage')
def self.indexed_param(key, values)
@ -27,11 +34,43 @@ module Fog
key << '.%d'
end
[*values].each_with_index do |value, index|
params[format(key, index + 1)] = value
if value.respond_to?('keys')
k = format(key, index + 1)
value.each do | vkey, vvalue |
params["#{k}.#{vkey}"] = vvalue
end
else
params[format(key, index + 1)] = value
end
end
params
end
def self.serialize_keys(key, value, options = {})
case value
when Hash
value.each do | k, v |
options.merge!(serialize_keys("#{key}.#{k}", v))
end
return options
when Array
value.each_with_index do | it, idx |
options.merge!(serialize_keys("#{key}.member.#{(idx + 1)}", it))
end
return options
else
return {key => value}
end
end
def self.indexed_request_param(name, values)
idx = -1
Array(values).inject({}) do |params, value|
params["#{name}.#{idx += 1}"] = value
params
end
end
def self.indexed_filters(filters)
params = {}
filters.keys.each_with_index do |key, key_index|
@ -60,6 +99,10 @@ module Fog
'Version' => options[:version]
})
params.merge!({
'SecurityToken' => options[:aws_session_token]
}) if options[:aws_session_token]
body = ''
for key in params.keys.sort
unless (value = params[key]).nil?
@ -100,10 +143,6 @@ module Fog
"ip-#{ip_address.gsub('.','-')}.ec2.internal"
end
def self.etag
Fog::Mock.random_hex(32)
end
def self.image
path = []
(rand(3) + 2).times do
@ -150,6 +189,10 @@ module Fog
ip.join('.')
end
def self.private_ip_address
ip_address.gsub(/^\d{1,3}\./,"10.")
end
def self.kernel_id
"aki-#{Fog::Mock.random_hex(8)}"
end
@ -193,6 +236,71 @@ module Fog
def self.volume_id
"vol-#{Fog::Mock.random_hex(8)}"
end
def self.security_group_id
"sg-#{Fog::Mock.random_hex(8)}"
end
def self.network_interface_id
"eni-#{Fog::Mock.random_hex(8)}"
end
def self.internet_gateway_id
"igw-#{Fog::Mock.random_hex(8)}"
end
def self.dhcp_options_id
"dopt-#{Fog::Mock.random_hex(8)}"
end
def self.vpc_id
"vpc-#{Fog::Mock.random_hex(8)}"
end
def self.subnet_id
"subnet-#{Fog::Mock.random_hex(8)}"
end
def self.zone_id
"zone-#{Fog::Mock.random_hex(8)}"
end
def self.change_id
"change-#{Fog::Mock.random_hex(8)}"
end
def self.nameservers
[
'ns-2048.awsdns-64.com',
'ns-2049.awsdns-65.net',
'ns-2050.awsdns-66.org',
'ns-2051.awsdns-67.co.uk'
]
end
def self.key_id(length=21)
#Probably close enough
Fog::Mock.random_selection('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',length)
end
def self.rds_address(db_name,region)
"#{db_name}.#{Fog::Mock.random_letters(rand(12) + 4)}.#{region}.rds.amazonaws.com"
end
end
def self.parse_security_group_options(group_name, options)
options ||= Hash.new
if group_name.is_a?(Hash)
options = group_name
elsif group_name
if options.key?('GroupName')
raise Fog::Compute::AWS::Error, 'Arguments specified both group_name and GroupName in options'
end
options = options.clone
options['GroupName'] = group_name
end
name_specified = options.key?('GroupName') && !options['GroupName'].nil?
group_id_specified = options.key?('GroupId') && !options['GroupId'].nil?
unless name_specified || group_id_specified
raise Fog::Compute::AWS::Error, 'Neither GroupName nor GroupId specified'
end
if name_specified && group_id_specified
options.delete('GroupName')
end
options
end
end
end

View file

@ -1,35 +1,44 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
module Fog
module AWS
class AutoScaling < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
class IdentifierTaken < Fog::Errors::Error; end
class ResourceInUse < Fog::Errors::Error; end
class ValidationError < Fog::Errors::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :host, :path, :port, :scheme, :persistent, :region
recognizes :host, :path, :port, :scheme, :persistent, :region, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at, :instrumentor, :instrumentor_name
request_path 'fog/aws/requests/auto_scaling'
request :create_auto_scaling_group
request :create_launch_configuration
request :create_or_update_tags
request :delete_auto_scaling_group
request :delete_launch_configuration
request :delete_notification_configuration
request :delete_policy
request :delete_scheduled_action
request :delete_tags
request :describe_adjustment_types
request :describe_auto_scaling_groups
request :describe_auto_scaling_instances
request :describe_auto_scaling_notification_types
request :describe_launch_configurations
request :describe_metric_collection_types
request :describe_notification_configurations
request :describe_policies
request :describe_scaling_activities
request :describe_scaling_process_types
request :describe_scheduled_actions
request :describe_tags
request :describe_termination_policy_types
request :disable_metrics_collection
request :enable_metrics_collection
request :execute_policy
request :put_notification_configuration
request :put_scaling_policy
request :put_scheduled_update_group_action
request :resume_processes
@ -48,8 +57,13 @@ module Fog
collection :groups
model :instance
collection :instances
model :policy
collection :policies
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
attr_accessor :region
# Initialize connection to AutoScaling
#
@ -68,29 +82,22 @@ module Fog
#
# ==== Returns
# * AutoScaling object with connection to AWS.
def initialize(options={})
require 'fog/core/parser'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@instrumentor = options[:instrumentor]
@instrumentor_name = options[:instrumentor_name] || 'fog.aws.auto_scaling'
options[:region] ||= 'us-east-1'
@host = options[:host] || case options[:region]
when 'ap-northeast-1'
'autoscaling.ap-northeast-1.amazonaws.com'
when 'ap-southeast-1'
'autoscaling.ap-southeast-1.amazonaws.com'
when 'eu-west-1'
'autoscaling.eu-west-1.amazonaws.com'
when 'us-east-1'
'autoscaling.us-east-1.amazonaws.com'
when 'us-west-1'
'autoscaling.us-west-1.amazonaws.com'
else
raise ArgumentError, "Unknown region: #{options[:region].inspect}"
end
@region = options[:region]
@host = options[:host] || "autoscaling.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@port = options[:port] || 443
@persistent = options[:persistent] || false
@ -105,6 +112,8 @@ module Fog
private
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
@ -112,14 +121,25 @@ module Fog
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,
:port => @port,
:version => '2010-08-01'
:version => '2011-01-01'
}
)
if @instrumentor
@instrumentor.instrument("#{@instrumentor_name}.request", params) do
_request(body, idempotent, parser)
end
else
_request(body, idempotent, parser)
end
end
def _request(body, idempotent, parser)
begin
response = @connection.request({
:body => body,
@ -151,9 +171,21 @@ module Fog
response
end
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
end
class Mock
include Fog::AWS::CredentialFetcher::ConnectionMethods
attr_accessor :region
def self.data
@data ||= Hash.new do |hash, region|
@ -166,10 +198,16 @@ module Fog
'PercentChangeInCapacity'
],
:auto_scaling_groups => {},
:health_states => ['Healthy', 'Unhealthy'],
:scaling_policies => {},
:health_states => [
'Healthy',
'Unhealthy'
],
:launch_configurations => {},
:metric_collection_types => {
:granularities => [ '1Minute' ],
:granularities => [
'1Minute'
],
:metrics => [
'GroupMinSize',
'GroupMaxSize',
@ -178,16 +216,33 @@ module Fog
'GroupPendingInstances',
'GroupTerminatingInstances',
'GroupTotalInstances'
],
]
},
:notification_configurations => {},
:notification_types => [
'autoscaling:EC2_INSTANCE_LAUNCH',
'autoscaling:EC2_INSTANCE_LAUNCH_ERROR',
'autoscaling:EC2_INSTANCE_TERMINATE',
'autoscaling:EC2_INSTANCE_TERMINATE_ERROR',
'autoscaling:TEST_NOTIFICATION'
],
:owner_id => owner_id,
:process_types => [
'AZRebalance',
'AddToLoadBalancer',
'AlarmNotification',
'HealthCheck',
'Launch',
'ReplaceUnhealthy',
'ScheduledActions',
'Terminate'
],
:termination_policy_types => [
'ClosestToNextInstanceHour',
'Default',
'NewestInstance',
'OldestInstance',
'OldestLaunchConfiguration'
]
}
end
@ -199,23 +254,29 @@ module Fog
end
def initialize(options={})
@aws_access_key_id = options[:aws_access_key_id]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@region = options[:region] || 'us-east-1'
unless ['ap-northeast-1', 'ap-southeast-1', 'eu-west-1', 'us-east-1', 'us-west-1'].include?(@region)
unless ['ap-northeast-1', 'ap-southeast-1', 'ap-southeast-2', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2'].include?(@region)
raise ArgumentError, "Unknown region: #{@region.inspect}"
end
end
def region_data
self.class.data[@region]
end
def data
self.class.data[@region][@aws_access_key_id]
self.region_data[@aws_access_key_id]
end
def reset_data
self.class.data[@region].delete(@aws_access_key_id)
self.region_data.delete(@aws_access_key_id)
end
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
end
end

152
lib/fog/aws/beanstalk.rb Normal file
View file

@ -0,0 +1,152 @@
require 'fog/aws'
module Fog
module AWS
class ElasticBeanstalk < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
class InvalidParameterError < Fog::Errors::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
request_path 'fog/aws/requests/beanstalk'
request :check_dns_availability
request :create_application
request :create_application_version
request :create_configuration_template
request :create_environment
request :create_storage_location
request :delete_application
request :delete_application_version
request :delete_configuration_template
request :delete_environment_configuration
request :describe_applications
request :describe_application_versions
request :describe_configuration_options
request :describe_configuration_settings
request :describe_environment_resources
request :describe_environments
request :describe_events
request :list_available_solution_stacks
request :rebuild_environment
request :request_environment_info
request :restart_app_server
request :retrieve_environment_info
request :swap_environment_cnames
request :terminate_environment
request :update_application
request :update_application_version
request :update_configuration_template
request :update_environment
request :validate_configuration_settings
model_path 'fog/aws/models/beanstalk'
model :application
collection :applications
model :environment
collection :environments
model :event
collection :events
model :template
collection :templates
model :version
collection :versions
class Mock
def initialize(options={})
Fog::Mock.not_implemented
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
def initialize(options={})
require 'fog/core/parser'
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
options[:region] ||= 'us-east-1'
@host = options[:host] || "elasticbeanstalk.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || 443
@scheme = options[:scheme] || 'https'
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", @persistent, @connection_options)
end
def reload
@connection.reset
end
# Returns an array of available solutions stack details
def solution_stacks
list_available_solution_stacks.body['ListAvailableSolutionStacksResult']['SolutionStackDetails']
end
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
body = AWS.signed_params(
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,
:port => @port,
:version => '2010-12-01'
}
)
begin
@connection.request({
:body => body,
:expects => 200,
:headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
:idempotent => idempotent,
:host => @host,
:method => 'POST',
:parser => parser
})
rescue Excon::Errors::HTTPStatusError => error
if match = error.response.body.match(/<Code>(.*)<\/Code>[ \t\n]*<Message>(.*)<\/Message>/)
raise case match[1].split('.').last
when 'InvalidParameterValue'
Fog::AWS::ElasticBeanstalk::InvalidParameterError.slurp(error, match[2])
else
Fog::AWS::ElasticBeanstalk::Error.slurp(error, "#{match[1]} => #{match[2]}")
end
else
raise error
end
end
end
end
end
end
end

View file

@ -1,14 +1,19 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
require 'fog/cdn'
module Fog
module CDN
class AWS < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
requires :aws_access_key_id, :aws_secret_access_key
recognizes :host, :path, :port, :scheme, :version, :persistent
recognizes :host, :path, :port, :scheme, :version, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
model_path 'fog/aws/cdn/models'
model_path 'fog/aws/models/cdn'
model :distribution
collection :distributions
model :streaming_distribution
collection :streaming_distributions
request_path 'fog/aws/requests/cdn'
request 'delete_distribution'
@ -16,6 +21,7 @@ module Fog
request 'get_distribution'
request 'get_distribution_list'
request 'get_invalidation_list'
request 'get_invalidation'
request 'get_streaming_distribution'
request 'get_streaming_distribution_list'
request 'post_distribution'
@ -27,12 +33,12 @@ module Fog
class Mock
def self.data
@data ||= Hash.new do |hash, region|
hash[region] = Hash.new do |region_hash, key|
region_hash[key] = {
:buckets => {}
@data ||= Hash.new do |hash, key|
hash[key] = {
:distributions => {},
:streaming_distributions => {},
:invalidations => {}
}
end
end
end
@ -42,26 +48,85 @@ module Fog
def initialize(options={})
require 'mime/types'
@aws_access_key_id = options[:aws_access_key_id]
@region = options[:region]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
end
def data
self.class.data[@region][@aws_access_key_id]
self.class.data[@aws_access_key_id]
end
def reset_data
self.class.data[@region].delete(@aws_access_key_id)
self.class.data.delete(@aws_access_key_id)
end
def signature(params)
"foo"
end
def setup_credentials(options={})
@aws_access_key_id = options[:aws_access_key_id]
end
def self.distribution_id
random_id(14)
end
def self.generic_id
random_id(14)
end
def self.domain_name
"#{random_id(12).downcase}.cloudfront.net"
end
def self.random_id(length)
Fog::Mock.random_selection("abcdefghijklmnopqrstuvwxyz0123456789", length).upcase
end
CDN_ERRORS = {
:access_denies => {:code => 'AccessDenied',:msg => 'Access denied.',:status => 403},
:inappropriate_xml => {:code => 'InappropriateXML',:msg => 'The XML document you provided was well-formed and valid, but not appropriate for this operation.',:status => 400},
:internal_error => {:code => 'InternalError',:msg => 'We encountered an internal error. Please try again.',:status => 500},
:invalid_action => {:code => 'InvalidAction',:msg => 'The action specified is not valid.',:status => 400},
:invalid_argument => {:code => 'InvalidArgument',:msg => '%s', :status => 400},
:not_implemented => {:code => 'NotImplemented', :msg => 'Not implemented.',:status => 501},
:no_such_distribution => { :code => 'NoSuchDistribution', :msg => 'The specified distribution does not exist', :status => 404 },
:no_such_streaming_distribution => { :code => 'NoSuchStreamingDistribution', :msg => 'The specified streaming distribution does not exist', :status => 404 },
:no_such_invalidation => { :code => 'NoSuchInvalidation', :msg => 'The specified invalidation does not exist', :status => 404 },
:cname_exists => { :code => 'CNAMEAlreadyExists', :msg => 'One or more of the CNAMEs you provided are already associated with a different distribution', :status => 409 },
:illegal_update => { :code => 'IllegalUpdate', :msg => 'Origin and CallerReference cannot be updated.', :status => 400 },
:invalid_if_match_version => { :code => 'InvalidIfMatchVersion', :msg => 'The If-Match version is missing or not valid for the distribution.', :status => 400},
:distribution_not_disabled => { :code => 'DistributionNotDisabled', :msg => 'The distribution you are trying to delete has not been disabled.', :status => 409 },
}
def self.error(code, argument = '')
if error = CDN_ERRORS[code]
raise_error(error[:status], error[:code], error[:msg] % argument)
end
end
def self.raise_error(status, code, message='')
response = Excon::Response.new
response.status = status
response.body = <<EOF
<ErrorResponse xmlns="http://cloudfront.amazonaws.com/doc/2010-11-01/">
<Error>
<Type>Sender</Type>
<Code>#{code}</Code>
<Message>#{message}.</Message>
</Error>
<RequestId>#{Fog::AWS::Mock.request_id}</RequestId>
</ErrorResponse>
EOF
raise(Excon::Errors.status_error({:expects => 201}, response))
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to Cloudfront
#
# ==== Notes
@ -82,13 +147,12 @@ module Fog
def initialize(options={})
require 'fog/core/parser'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@hmac = Fog::HMAC.new('sha1', @aws_secret_access_key)
@host = options[:host] || 'cloudfront.amazonaws.com'
@path = options[:path] || '/'
@persistent = options[:persistent] || true
@persistent = options.fetch(:persistent, true)
@port = options[:port] || 443
@scheme = options[:scheme] || 'https'
@version = options[:version] || '2010-11-01'
@ -101,9 +165,21 @@ module Fog
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha1', @aws_secret_access_key)
end
def request(params, &block)
refresh_credentials_if_expired
params[:headers] ||= {}
params[:headers]['Date'] = Fog::Time.now.to_date_header
params[:headers]['x-amz-security-token'] = @aws_session_token if @aws_session_token
params[:headers]['Authorization'] = "AWS #{@aws_access_key_id}:#{signature(params)}"
params[:path] = "/#{@version}/#{params[:path]}"
@connection.request(params, &block)
@ -112,7 +188,7 @@ module Fog
def signature(params)
string_to_sign = params[:headers]['Date']
signed_string = @hmac.sign(string_to_sign)
signature = Base64.encode64(signed_string).chomp!
Base64.encode64(signed_string).chomp!
end
end
end

View file

@ -1,14 +1,16 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
module Fog
module AWS
class CloudFormation < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
requires :aws_access_key_id, :aws_secret_access_key
recognizes :host, :path, :port, :scheme, :persistent, :region
recognizes :host, :path, :port, :scheme, :persistent, :region, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
request_path 'fog/aws/requests/cloud_formation'
request :create_stack
request :update_stack
request :delete_stack
request :describe_stack_events
request :describe_stack_resources
@ -25,7 +27,7 @@ module Fog
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to CloudFormation
#
# ==== Notes
@ -45,28 +47,13 @@ module Fog
# * CloudFormation object with connection to AWS.
def initialize(options={})
require 'fog/core/parser'
require 'multi_json'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
options[:region] ||= 'us-east-1'
@host = options[:host] || case options[:region]
when 'ap-northeast-1'
'cloudformation.ap-northeast-1.amazonaws.com'
when 'ap-southeast-1'
'cloudformation.ap-southeast-1.amazonaws.com'
when 'eu-west-1'
'cloudformation.eu-west-1.amazonaws.com'
when 'us-east-1'
'cloudformation.us-east-1.amazonaws.com'
when 'us-west-1'
'cloudformation.us-west-1.amazonaws.com'
else
raise ArgumentError, "Unknown region: #{options[:region].inspect}"
end
@host = options[:host] || "cloudformation.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || 443
@ -80,7 +67,18 @@ module Fog
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
@ -88,6 +86,7 @@ module Fog
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,

View file

@ -1,34 +1,76 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
module Fog
module AWS
class CloudWatch < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
requires :aws_access_key_id, :aws_secret_access_key
recognizes :region, :host, :path, :port, :scheme, :persistent
recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at, :instrumentor, :instrumentor_name
request_path 'fog/aws/requests/cloud_watch'
request :list_metrics
request :get_metric_statistics
request :put_metric_data
request :describe_alarms
request :put_metric_alarm
request :delete_alarms
request :describe_alarm_history
request :enable_alarm_actions
request :disable_alarm_actions
request :describe_alarms_for_metric
request :set_alarm_state
model_path 'fog/aws/models/cloud_watch'
model :metric
collection :metrics
model :metric_statistic
collection :metric_statistics
model :alarm_datum
collection :alarm_data
model :alarm_history
collection :alarm_histories
model :alarm
collection :alarms
class Mock
def initialize(options={})
def self.data
@data ||= Hash.new do |hash, region|
hash[region] = Hash.new do |region_hash, key|
region_hash[key] = {
:metric_alarms => {}
}
end
end
end
def self.reset
@data = nil
end
def initialize(options={})
@aws_access_key_id = options[:aws_access_key_id]
@region = options[:region] || 'us-east-1'
unless ['ap-northeast-1', 'ap-southeast-1', 'ap-southeast-2', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2'].include?(@region)
raise ArgumentError, "Unknown region: #{@region.inspect}"
end
end
def data
self.class.data[@region][@aws_access_key_id]
end
def reset_data
self.class.data[@region].delete(@aws_access_key_id)
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to Cloudwatch
#
# ==== Notes
@ -43,31 +85,21 @@ module Fog
#
# ==== Parameters
# * options<~Hash> - config arguments for connection. Defaults to {}.
# * region<~String> - optional region to use, in ['eu-west-1', 'us-east-1', 'us-west-1', 'ap-southeast-1', 'ap-northeast-1']
# * region<~String> - optional region to use. For instance, 'eu-west-1', 'us-east-1', etc.
#
# ==== Returns
# * CloudWatch object with connection to AWS.
def initialize(options={})
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@instrumentor = options[:instrumentor]
@instrumentor_name = options[:instrumentor_name] || 'fog.aws.cloud_watch'
options[:region] ||= 'us-east-1'
@host = options[:host] || case options[:region]
when 'ap-northeast-1'
'monitoring.ap-northeast-1.amazonaws.com'
when 'ap-southeast-1'
'monitoring.ap-southeast-1.amazonaws.com'
when 'eu-west-1'
'monitoring.eu-west-1.amazonaws.com'
when 'us-east-1'
'monitoring.us-east-1.amazonaws.com'
when 'us-west-1'
'monitoring.us-west-1.amazonaws.com'
else
raise ArgumentError, "Unknown region: #{options[:region].inspect}"
end
@host = options[:host] || "monitoring.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || 443
@ -81,7 +113,17 @@ module Fog
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
@ -89,6 +131,7 @@ module Fog
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,
@ -97,7 +140,17 @@ module Fog
}
)
response = @connection.request({
if @instrumentor
@instrumentor.instrument("#{@instrumentor_name}.request", params) do
_request(body, idempotent, parser)
end
else
_request(body, idempotent, parser)
end
end
def _request(body, idempotent, parser)
@connection.request({
:body => body,
:expects => 200,
:headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },

View file

@ -1,22 +1,31 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
require 'fog/compute'
module Fog
module Compute
class AWS < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
requires :aws_access_key_id, :aws_secret_access_key
recognizes :endpoint, :region, :host, :path, :port, :scheme, :persistent, :connection_options
recognizes :endpoint, :region, :host, :path, :port, :scheme, :persistent, :aws_session_token, :use_iam_profile, :aws_credentials_expire_at, :instrumentor, :instrumentor_name, :version
secrets :aws_secret_access_key, :hmac, :aws_session_token
model_path 'fog/aws/models/compute'
model :address
collection :addresses
model :dhcp_options
collection :dhcp_options
model :flavor
collection :flavors
model :image
collection :images
model :internet_gateway
collection :internet_gateways
model :key_pair
collection :key_pairs
model :network_interface
collection :network_interfaces
model :security_group
collection :security_groups
model :server
@ -29,35 +38,58 @@ module Fog
collection :volumes
model :spot_request
collection :spot_requests
model :subnet
collection :subnets
model :vpc
collection :vpcs
request_path 'fog/aws/requests/compute'
request :allocate_address
request :associate_address
request :associate_dhcp_options
request :attach_network_interface
request :attach_internet_gateway
request :attach_volume
request :authorize_security_group_ingress
request :cancel_spot_instance_requests
request :create_dhcp_options
request :create_internet_gateway
request :create_image
request :create_key_pair
request :create_network_interface
request :create_placement_group
request :create_security_group
request :create_snapshot
request :create_spot_datafeed_subscription
request :create_subnet
request :create_tags
request :create_volume
request :create_vpc
request :copy_snapshot
request :delete_dhcp_options
request :delete_internet_gateway
request :delete_key_pair
request :delete_network_interface
request :delete_security_group
request :delete_placement_group
request :delete_snapshot
request :delete_spot_datafeed_subscription
request :delete_subnet
request :delete_tags
request :delete_volume
request :delete_vpc
request :deregister_image
request :describe_addresses
request :describe_availability_zones
request :describe_dhcp_options
request :describe_images
request :describe_instances
request :describe_internet_gateways
request :describe_reserved_instances
request :describe_instance_status
request :describe_key_pairs
request :describe_network_interface_attribute
request :describe_network_interfaces
request :describe_placement_groups
request :describe_regions
request :describe_reserved_instances_offerings
@ -66,8 +98,13 @@ module Fog
request :describe_spot_datafeed_subscription
request :describe_spot_instance_requests
request :describe_spot_price_history
request :describe_subnets
request :describe_tags
request :describe_volumes
request :describe_volume_status
request :describe_vpcs
request :detach_network_interface
request :detach_internet_gateway
request :detach_volume
request :disassociate_address
request :get_console_output
@ -75,12 +112,15 @@ module Fog
request :import_key_pair
request :modify_image_attribute
request :modify_instance_attribute
request :modify_network_interface_attribute
request :modify_snapshot_attribute
request :modify_volume_attribute
request :purchase_reserved_instances_offering
request :reboot_instances
request :release_address
request :register_image
request :request_spot_instances
request :reset_network_interface_attribute
request :revoke_security_group_ingress
request :run_instances
request :terminate_instances
@ -93,22 +133,29 @@ module Fog
class Real
def modify_image_attributes(*params)
Fog::Logger.warning("modify_image_attributes is deprecated, use modify_image_attribute instead [light_black](#{caller.first})[/]")
Fog::Logger.deprecation("modify_image_attributes is deprecated, use modify_image_attribute instead [light_black](#{caller.first})[/]")
modify_image_attribute(*params)
end
end
class Mock
include Fog::AWS::CredentialFetcher::ConnectionMethods
def self.data
@data ||= Hash.new do |hash, region|
owner_id = Fog::AWS::Mock.owner_id
hash[region] = Hash.new do |region_hash, key|
owner_id = Fog::AWS::Mock.owner_id
security_group_id = Fog::AWS::Mock.security_group_id
region_hash[key] = {
:deleted_at => {},
:addresses => {},
:images => {},
:image_launch_permissions => Hash.new do |permissions_hash, image_key|
permissions_hash[image_key] = {
:users => []
}
end,
:instances => {},
:reserved_instances => {},
:key_pairs => {},
@ -118,24 +165,25 @@ module Fog
'default' => {
'groupDescription' => 'default group',
'groupName' => 'default',
'groupId' => security_group_id,
'ipPermissionsEgress' => [],
'ipPermissions' => [
{
'groups' => [{'groupName' => 'default', 'userId' => owner_id}],
'groups' => [{'groupName' => 'default', 'userId' => owner_id, 'groupId' => security_group_id }],
'fromPort' => -1,
'toPort' => -1,
'ipProtocol' => 'icmp',
'ipRanges' => []
},
{
'groups' => [{'groupName' => 'default', 'userId' => owner_id}],
'groups' => [{'groupName' => 'default', 'userId' => owner_id, 'groupId' => security_group_id}],
'fromPort' => 0,
'toPort' => 65535,
'ipProtocol' => 'tcp',
'ipRanges' => []
},
{
'groups' => [{'groupName' => 'default', 'userId' => owner_id}],
'groups' => [{'groupName' => 'default', 'userId' => owner_id, 'groupId' => security_group_id}],
'fromPort' => 0,
'toPort' => 65535,
'ipProtocol' => 'udp',
@ -145,9 +193,18 @@ module Fog
'ownerId' => owner_id
}
},
:network_interfaces => {},
:snapshots => {},
:volumes => {},
:tags => {}
:internet_gateways => {},
:tags => {},
:tag_sets => Hash.new do |tag_set_hash, resource_id|
tag_set_hash[resource_id] = {}
end,
:subnets => [],
:vpcs => [],
:dhcp_options => [],
:internet_gateways => []
}
end
end
@ -157,56 +214,85 @@ module Fog
@data = nil
end
def initialize(options={})
@aws_access_key_id = options[:aws_access_key_id]
attr_accessor :region
def initialize(options={})
@use_iam_profile = options[:use_iam_profile]
@aws_credentials_expire_at = Time::now + 20
setup_credentials(options)
@region = options[:region] || 'us-east-1'
unless ['ap-northeast-1', 'ap-southeast-1', 'eu-west-1', 'us-east-1', 'us-west-1'].include?(@region)
unless ['ap-northeast-1', 'ap-southeast-1', 'ap-southeast-2', 'eu-west-1', 'us-east-1', 'us-west-1', 'us-west-2', 'sa-east-1'].include?(@region)
raise ArgumentError, "Unknown region: #{@region.inspect}"
end
end
def region_data
self.class.data[@region]
end
def data
self.class.data[@region][@aws_access_key_id]
self.region_data[@aws_access_key_id]
end
def reset_data
self.class.data[@region].delete(@aws_access_key_id)
self.region_data.delete(@aws_access_key_id)
end
def apply_tag_filters(resources, filters)
def visible_images
images = self.data[:images].values.inject({}) do |h, image|
h.update(image['imageId'] => image)
end
self.region_data.each do |aws_access_key_id, data|
data[:image_launch_permissions].each do |image_id, list|
if list[:users].include?(self.data[:owner_id])
images.update(image_id => data[:images][image_id])
end
end
end
images
end
def apply_tag_filters(resources, filters, resource_id_key)
tag_set_fetcher = lambda {|resource| self.data[:tag_sets][resource[resource_id_key]] }
# tag-key: match resources tagged with this key (any value)
if filters.has_key?('tag-key')
value = filters.delete('tag-key')
resources = resources.select{|r| r['tagSet'].has_key?(value)}
resources = resources.select{|r| tag_set_fetcher[r].has_key?(value)}
end
# tag-value: match resources tagged with this value (any key)
if filters.has_key?('tag-value')
value = filters.delete('tag-value')
resources = resources.select{|r| r['tagSet'].values.include?(value)}
resources = resources.select{|r| tag_set_fetcher[r].values.include?(value)}
end
# tag:key: match resources taged with a key-value pair. Value may be an array, which is OR'd.
# tag:key: match resources tagged with a key-value pair. Value may be an array, which is OR'd.
tag_filters = {}
filters.keys.each do |key|
filters.keys.each do |key|
tag_filters[key.gsub('tag:', '')] = filters.delete(key) if /^tag:/ =~ key
end
for tag_key, tag_value in tag_filters
resources = resources.select{|r| tag_value.include?(r['tagSet'][tag_key])}
resources = resources.select{|r| tag_value.include?(tag_set_fetcher[r][tag_key])}
end
resources
end
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to EC2
#
# ==== Notes
# options parameter must include values for :aws_access_key_id and
# options parameter must include values for :aws_access_key_id and
# :aws_secret_access_key in order to create a connection
#
# ==== Examples
@ -217,19 +303,25 @@ module Fog
#
# ==== Parameters
# * options<~Hash> - config arguments for connection. Defaults to {}.
# * region<~String> - optional region to use, in
# ['eu-west-1', 'us-east-1', 'us-west-1', 'ap-northeast-1', 'ap-southeast-1']
# * region<~String> - optional region to use. For instance,
# 'eu-west-1', 'us-east-1', and etc.
# * aws_session_token<~String> - when using Session Tokens or Federated Users, a session_token must be presented
#
# ==== Returns
# * EC2 object with connection to aws.
attr_accessor :region
def initialize(options={})
require 'fog/core/parser'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@region = options[:region] ||= 'us-east-1'
@instrumentor = options[:instrumentor]
@instrumentor_name = options[:instrumentor_name] || 'fog.aws.compute'
@version = options[:version] || '2012-12-01'
if @endpoint = options[:endpoint]
endpoint = URI.parse(@endpoint)
@ -238,20 +330,7 @@ module Fog
@port = endpoint.port
@scheme = endpoint.scheme
else
@host = options[:host] || case options[:region]
when 'ap-northeast-1'
'ec2.ap-northeast-1.amazonaws.com'
when 'ap-southeast-1'
'ec2.ap-southeast-1.amazonaws.com'
when 'eu-west-1'
'ec2.eu-west-1.amazonaws.com'
when 'us-east-1'
'ec2.us-east-1.amazonaws.com'
when 'us-west-1'
'ec2.us-west-1.amazonaws.com'
else
raise ArgumentError, "Unknown region: #{options[:region].inspect}"
end
@host = options[:host] || "ec2.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || 443
@ -265,8 +344,17 @@ module Fog
end
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
@ -274,16 +362,26 @@ module Fog
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,
:port => @port,
:version => '2011-05-15'
:version => @version
}
)
begin
response = @connection.request({
if @instrumentor
@instrumentor.instrument("#{@instrumentor_name}.request", params) do
_request(body, idempotent, parser)
end
else
_request(body, idempotent, parser)
end
end
def _request(body, idempotent, parser)
@connection.request({
:body => body,
:expects => 200,
:headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
@ -292,20 +390,17 @@ module Fog
:method => 'POST',
:parser => parser
})
rescue Excon::Errors::HTTPStatusError => error
if match = error.message.match(/<Code>(.*)<\/Code><Message>(.*)<\/Message>/)
raise case match[1].split('.').last
when 'NotFound', 'Unknown'
Fog::Compute::AWS::NotFound.slurp(error, match[2])
else
Fog::Compute::AWS::Error.slurp(error, "#{match[1]} => #{match[2]}")
end
else
raise error
end
rescue Excon::Errors::HTTPStatusError => error
if match = error.message.match(/<Code>(.*)<\/Code><Message>(.*)<\/Message>/)
raise case match[1].split('.').last
when 'NotFound', 'Unknown'
Fog::Compute::AWS::NotFound.slurp(error, match[2])
else
Fog::Compute::AWS::Error.slurp(error, "#{match[1]} => #{match[2]}")
end
else
raise error
end
response
end
end

View file

@ -0,0 +1,64 @@
require 'fog/core/json'
module Fog
module AWS
module CredentialFetcher
INSTANCE_METADATA_HOST = "http://169.254.169.254"
INSTANCE_METADATA_PATH = "/latest/meta-data/iam/security-credentials/"
module ServiceMethods
def fetch_credentials(options)
if options[:use_iam_profile]
begin
connection = options[:connection] || Excon.new(INSTANCE_METADATA_HOST)
role_name = connection.get(:path => INSTANCE_METADATA_PATH, :expects => 200).body
role_data = connection.get(:path => INSTANCE_METADATA_PATH+role_name, :expects => 200).body
session = Fog::JSON.decode(role_data)
credentials = {}
credentials[:aws_access_key_id] = session['AccessKeyId']
credentials[:aws_secret_access_key] = session['SecretAccessKey']
credentials[:aws_session_token] = session['Token']
credentials[:aws_credentials_expire_at] = Time.xmlschema session['Expiration']
#these indicate the metadata service is unavailable or has no profile setup
credentials
rescue Excon::Errors::Error => e
Fog::Logger.warning("Unable to fetch credentuals: #{e.message}")
super
end
else
super
end
end
end
module ConnectionMethods
def refresh_credentials_if_expired
refresh_credentials if credentials_expired?
end
private
def credentials_expired?
@use_iam_profile &&
(!@aws_credentials_expire_at ||
(@aws_credentials_expire_at && Fog::Time.now > @aws_credentials_expire_at - 15)) #new credentials become available from around 5 minutes before expiration time
end
def refresh_credentials
if @use_iam_profile
new_credentials = service.fetch_credentials :use_iam_profile => @use_iam_profile
if new_credentials.any?
setup_credentials new_credentials
return true
else
false
end
else
false
end
end
end
end
end
end

View file

@ -1,12 +1,13 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
require 'fog/dns'
module Fog
module DNS
class AWS < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
requires :aws_access_key_id, :aws_secret_access_key
recognizes :host, :path, :port, :scheme, :version, :persistent
recognizes :host, :path, :port, :scheme, :version, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
model_path 'fog/aws/models/dns'
model :record
@ -29,7 +30,11 @@ module Fog
@data ||= Hash.new do |hash, region|
hash[region] = Hash.new do |region_hash, key|
region_hash[key] = {
:buckets => {}
:buckets => {},
:limits => {
:duplicate_domains => 5
},
:zones => {}
}
end
end
@ -41,7 +46,8 @@ module Fog
def initialize(options={})
require 'mime/types'
@aws_access_key_id = options[:aws_access_key_id]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@region = options[:region]
end
@ -56,10 +62,14 @@ module Fog
def signature(params)
"foo"
end
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to Route 53 DNS service
#
# ==== Notes
@ -80,16 +90,15 @@ module Fog
def initialize(options={})
require 'fog/core/parser'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@hmac = Fog::HMAC.new('sha1', @aws_secret_access_key)
@host = options[:host] || 'route53.amazonaws.com'
@path = options[:path] || '/'
@persistent = options[:persistent] || true
@persistent = options.fetch(:persistent, true)
@port = options[:port] || 443
@scheme = options[:scheme] || 'https'
@version = options[:version] || '2010-10-01'
@version = options[:version] || '2012-02-29'
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", @persistent, @connection_options)
end
@ -100,18 +109,29 @@ module Fog
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha1', @aws_secret_access_key)
end
def request(params, &block)
refresh_credentials_if_expired
params[:headers] ||= {}
params[:headers]['Date'] = Fog::Time.now.to_date_header
params[:headers]['x-amz-security-token'] = @aws_session_token if @aws_session_token
params[:headers]['X-Amzn-Authorization'] = "AWS3-HTTPS AWSAccessKeyId=#{@aws_access_key_id},Algorithm=HmacSHA1,Signature=#{signature(params)}"
params[:path] = "/#{@version}/#{params[:path]}"
params[:path] = "/#{@version}/#{params[:path]}"
@connection.request(params, &block)
end
def signature(params)
string_to_sign = params[:headers]['Date']
signed_string = @hmac.sign(string_to_sign)
signature = Base64.encode64(signed_string).chomp!
Base64.encode64(signed_string).chomp!
end
end
end

142
lib/fog/aws/dynamodb.rb Normal file
View file

@ -0,0 +1,142 @@
require 'fog/aws'
module Fog
module AWS
class DynamoDB < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
requires :aws_access_key_id, :aws_secret_access_key
recognizes :aws_session_token, :host, :path, :port, :scheme, :persistent, :region, :use_iam_profile, :aws_credentials_expire_at
request_path 'fog/aws/requests/dynamodb'
request :batch_get_item
request :batch_write_item
request :create_table
request :delete_item
request :delete_table
request :describe_table
request :get_item
request :list_tables
request :put_item
request :query
request :scan
request :update_item
request :update_table
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {
:domains => {}
}
end
end
def self.reset
@data = nil
end
def initialize(options={})
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
end
def data
self.class.data[@aws_access_key_id]
end
def reset_data
self.class.data.delete(@aws_access_key_id)
end
def setup_credientials(options)
@aws_access_key_id = options[:aws_access_key_id]
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to DynamoDB
#
# ==== Notes
# options parameter must include values for :aws_access_key_id and
# :aws_secret_access_key in order to create a connection
#
# ==== Examples
# ddb = DynamoDB.new(
# :aws_access_key_id => your_aws_access_key_id,
# :aws_secret_access_key => your_aws_secret_access_key
# )
#
# ==== Parameters
# * options<~Hash> - config arguments for connection. Defaults to {}.
#
# ==== Returns
# * DynamoDB object with connection to aws
def initialize(options={})
@use_iam_profile = options[:use_iam_profile]
@region = options[:region] || 'us-east-1'
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@host = options[:host] || "dynamodb.#{@region}.amazonaws.com"
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || '80' #443
@scheme = options[:scheme] || 'https'
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", @persistent, @connection_options)
end
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key, @region, 'dynamodb')
end
def reload
@connection.reset
end
def request(params)
refresh_credentials_if_expired
# defaults for all dynamodb requests
params.merge!({
:expects => 200,
:host => @host,
:method => :post,
:path => '/'
})
# setup headers and sign with signature v4
date = Fog::Time.now
params[:headers] = {
'Content-Type' => 'application/x-amz-json-1.0',
'Date' => date.to_iso8601_basic,
'Host' => @host,
}.merge!(params[:headers])
params[:headers]['x-amz-security-token'] = @aws_session_token if @aws_session_token
params[:headers]['Authorization'] = @signer.sign(params, date)
response = @connection.request(params)
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
end
response
end
end
end
end
end

View file

@ -1,12 +1,13 @@
module Fog
module AWS
class Elasticache < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
class IdentifierTaken < Fog::Errors::Error; end
class InvalidInstance < Fog::Errors::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :region, :host, :path, :port, :scheme, :persistent
recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
request_path 'fog/aws/requests/elasticache'
@ -40,27 +41,14 @@ module Fog
model :parameter_group
collection :parameter_groups
class Mock
def initalize(options={})
Fog::Mock.not_implemented
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
def initialize(options={})
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
options[:region] ||= 'us-east-1'
@host = options[:host] || case options[:region]
when 'us-east-1'
'elasticache.us-east-1.amazonaws.com'
#TODO: Support other regions
else
raise ArgumentError, "Unknown region: #{options[:region].inspect}"
end
@host = options[:host] || "elasticache.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@port = options[:port] || 443
@scheme = options[:scheme] || 'https'
@ -74,7 +62,19 @@ module Fog
end
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
@ -82,6 +82,7 @@ module Fog
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,
@ -122,6 +123,70 @@ module Fog
end
end
class Mock
include Fog::AWS::CredentialFetcher::ConnectionMethods
def self.data
@data ||= Hash.new do |hash, region|
hash[region] = Hash.new do |region_hash, key|
owner_id = Fog::AWS::Mock.owner_id
security_group_id = Fog::AWS::Mock.security_group_id
region_hash[key] = {
:clusters => {}, # cache cluster data, indexed by cluster ID
}
end
end
end
def self.reset
@data = nil
end
def initialize(options={})
@aws_credentials_expire_at = Time::now + 20
setup_credentials(options)
@region = options[:region] || 'us-east-1'
unless ['ap-northeast-1', 'ap-southeast-1', 'eu-west-1', 'us-east-1',
'us-west-1', 'us-west-2', 'sa-east-1'].include?(@region)
raise ArgumentError, "Unknown region: #{@region.inspect}"
end
end
def region_data
self.class.data[@region]
end
def data
self.region_data[@aws_access_key_id]
end
def reset_data
self.region_data.delete(@aws_access_key_id)
end
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
end
# returns an Array of (Mock) elasticache nodes, representated as Hashes
def create_cache_nodes(cluster_id, num_nodes = 1, port = '11211')
(1..num_nodes).map do |node_number|
node_id = "%04d" % node_number
{ # each hash represents a cache cluster node
"CacheNodeId" => node_id,
"Port" => port,
"ParameterGroupStatus" => "in-sync",
"CacheNodeStatus" => "available",
"CacheNodeCreateTime" => Time.now.utc.to_s,
"Address" =>
"#{cluster_id}.#{node_id}.use1.cache.amazonaws.com"
}
end
end
end
end
end
end

View file

@ -1,15 +1,22 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
module Fog
module AWS
class ELB < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
class IdentifierTaken < Fog::Errors::Error; end
class InvalidInstance < Fog::Errors::Error; end
class Throttled < Fog::Errors::Error; end
class DuplicatePolicyName < Fog::Errors::Error; end
class IdentifierTaken < Fog::Errors::Error; end
class InvalidInstance < Fog::Errors::Error; end
class InvalidConfigurationRequest < Fog::Errors::Error; end
class PolicyNotFound < Fog::Errors::Error; end
class PolicyTypeNotFound < Fog::Errors::Error; end
class Throttled < Fog::Errors::Error; end
class TooManyPolicies < Fog::Errors::Error; end
class ValidationError < Fog::Errors::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :region, :host, :path, :port, :scheme, :persistent
recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at, :instrumentor, :instrumentor_name
request_path 'fog/aws/requests/elb'
request :configure_health_check
@ -17,17 +24,23 @@ module Fog
request :create_lb_cookie_stickiness_policy
request :create_load_balancer
request :create_load_balancer_listeners
request :create_load_balancer_policy
request :delete_load_balancer
request :delete_load_balancer_listeners
request :delete_load_balancer_policy
request :deregister_instances_from_load_balancer
request :describe_instance_health
request :describe_load_balancers
request :describe_load_balancer_policies
request :describe_load_balancer_policy_types
request :disable_availability_zones_for_load_balancer
request :enable_availability_zones_for_load_balancer
request :register_instances_with_load_balancer
request :set_load_balancer_listener_ssl_certificate
request :set_load_balancer_policies_of_listener
request :attach_load_balancer_to_subnets
request :detach_load_balancer_from_subnets
request :apply_security_groups_to_load_balancer
model_path 'fog/aws/models/elb'
model :load_balancer
@ -39,13 +52,16 @@ module Fog
class Mock
require 'fog/aws/elb/policy_types'
def self.data
@data ||= Hash.new do |hash, region|
owner_id = Fog::AWS::Mock.owner_id
hash[region] = Hash.new do |region_hash, key|
region_hash[key] = {
:owner_id => owner_id,
:load_balancers => {}
:load_balancers => {},
:policy_types => Fog::AWS::ELB::Mock::POLICY_TYPES
}
end
end
@ -60,15 +76,20 @@ module Fog
end
def initialize(options={})
@aws_access_key_id = options[:aws_access_key_id]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@region = options[:region] || 'us-east-1'
unless ['ap-northeast-1', 'ap-southeast-1', 'eu-west-1', 'us-east-1', 'us-west-1'].include?(@region)
unless ['ap-northeast-1', 'ap-southeast-1', 'ap-southeast-2', 'eu-west-1', 'us-east-1', 'us-west-1', 'us-west-2'].include?(@region)
raise ArgumentError, "Unknown region: #{@region.inspect}"
end
end
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
end
def data
self.class.data[@region][@aws_access_key_id]
end
@ -79,7 +100,7 @@ module Fog
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to ELB
#
# ==== Notes
@ -94,33 +115,21 @@ module Fog
#
# ==== Parameters
# * options<~Hash> - config arguments for connection. Defaults to {}.
# * region<~String> - optional region to use, in ['eu-west-1', 'us-east-1', 'us-west-1', 'ap-northeast-1', 'ap-southeast-1']
# * region<~String> - optional region to use. For instance, 'eu-west-1', 'us-east-1', etc.
#
# ==== Returns
# * ELB object with connection to AWS.
def initialize(options={})
require 'fog/core/parser'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@instrumentor = options[:instrumentor]
@instrumentor_name = options[:instrumentor_name] || 'fog.aws.elb'
options[:region] ||= 'us-east-1'
@host = options[:host] || case options[:region]
when 'ap-northeast-1'
'elasticloadbalancing.ap-northeast-1.amazonaws.com'
when 'ap-southeast-1'
'elasticloadbalancing.ap-southeast-1.amazonaws.com'
when 'eu-west-1'
'elasticloadbalancing.eu-west-1.amazonaws.com'
when 'us-east-1'
'elasticloadbalancing.us-east-1.amazonaws.com'
when 'us-west-1'
'elasticloadbalancing.us-west-1.amazonaws.com'
else
raise ArgumentError, "Unknown region: #{options[:region].inspect}"
end
@host = options[:host] || "elasticloadbalancing.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || 443
@ -134,7 +143,18 @@ module Fog
private
def setup_credentials(options={})
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
@ -142,15 +162,26 @@ module Fog
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,
:port => @port,
:version => '2011-04-05'
:version => '2012-06-01'
}
)
response = @connection.request({
if @instrumentor
@instrumentor.instrument("#{@instrumentor_name}.request", params) do
_request(body, idempotent, parser)
end
else
_request(body, idempotent, parser)
end
end
def _request(body, idempotent, parser)
@connection.request({
:body => body,
:expects => 200,
:headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
@ -164,14 +195,27 @@ module Fog
case match[1]
when 'CertificateNotFound'
raise Fog::AWS::IAM::NotFound.slurp(error, match[2])
when 'LoadBalancerNotFound'
raise Fog::AWS::ELB::NotFound.slurp(error, match[2])
when 'DuplicateLoadBalancerName'
raise Fog::AWS::ELB::IdentifierTaken.slurp(error, match[2])
when 'DuplicatePolicyName'
raise Fog::AWS::ELB::DuplicatePolicyName.slurp(error, match[2])
when 'InvalidInstance'
raise Fog::AWS::ELB::InvalidInstance.slurp(error, match[2])
when 'InvalidConfigurationRequest'
# when do they fucking use this shit?
raise Fog::AWS::ELB::InvalidConfigurationRequest.slurp(error, match[2])
when 'LoadBalancerNotFound'
raise Fog::AWS::ELB::NotFound.slurp(error, match[2])
when 'PolicyNotFound'
raise Fog::AWS::ELB::PolicyNotFound.slurp(error, match[2])
when 'PolicyTypeNotFound'
raise Fog::AWS::ELB::PolicyTypeNotFound.slurp(error, match[2])
when 'Throttling'
raise Fog::AWS::ELB::Throttled.slurp(error, match[2])
when 'TooManyPolicies'
raise Fog::AWS::ELB::TooManyPolicies.slurp(error, match[2])
when 'ValidationError'
raise Fog::AWS::ELB::ValidationError.slurp(error, match[2])
else
raise
end

View file

@ -0,0 +1,466 @@
class Fog::AWS::ELB::Mock
POLICY_TYPES = [{
"Description" => "",
"PolicyAttributeTypeDescriptions" => [{
"AttributeName"=>"CookieName",
"AttributeType"=>"String",
"Cardinality"=>"ONE",
"DefaultValue"=>"",
"Description"=>""
}],
"PolicyTypeName"=>"AppCookieStickinessPolicyType"
},
{
"Description" => "",
"PolicyAttributeTypeDescriptions" => [{
"AttributeName"=>"CookieExpirationPeriod",
"AttributeType"=>"String",
"Cardinality"=>"ONE",
"DefaultValue"=>"",
"Description"=>""
}],
"PolicyTypeName"=>"LBCookieStickinessPolicyType"
},
{
"Description" => "Policy containing a list of public keys to accept when authenticating the back-end server(s). This policy cannot be applied directly to back-end servers or listeners but must be part of a BackendServerAuthenticationPolicyType.",
"PolicyAttributeTypeDescriptions" => [{
"AttributeName"=>"PublicKey",
"AttributeType"=>"String",
"Cardinality"=>"ONE",
"DefaultValue"=>"",
"Description"=>""
}],
"PolicyTypeName"=>"PublicKeyPolicyType"
},
{
"Description" => "Listener policy that defines the ciphers and protocols that will be accepted by the load balancer. This policy can be associated only with HTTPS/SSL listeners.",
"PolicyAttributeTypeDescriptions" => [{
"AttributeName"=>"Protocol-SSLv2",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EDH-DSS-DES-CBC3-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-RSA-CAMELLIA128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DES-CBC-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"KRB5-RC4-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-CAMELLIA128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-KRB5-RC4-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-RC4-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"PSK-RC4-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"PSK-AES128-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-EDH-RSA-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"CAMELLIA128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-DSS-AES128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EDH-RSA-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-RSA-SEED-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"KRB5-DES-CBC-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-RSA-CAMELLIA256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-DES-CBC3-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DES-CBC3-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-KRB5-RC2-CBC-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EDH-DSS-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"KRB5-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"PSK-AES256-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-AES256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"KRB5-DES-CBC3-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"AES128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"TRUE",
"Description"=>""
},
{
"AttributeName"=>"DHE-DSS-SEED-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-CAMELLIA256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-KRB5-RC4-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EDH-RSA-DES-CBC3-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-KRB5-DES-CBC-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"Protocol-TLSv1",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"TRUE",
"Description"=>""
},
{
"AttributeName"=>"PSK-3DES-EDE-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"SEED-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-DSS-CAMELLIA256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"IDEA-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"RC2-CBC-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"KRB5-RC4-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-AES128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"RC4-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"TRUE",
"Description"=>""
},
{
"AttributeName"=>"AES256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"TRUE",
"Description"=>""
},
{
"AttributeName"=>"Protocol-SSLv3",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"TRUE",
"Description"=>""
},
{
"AttributeName"=>"EXP-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DES-CBC3-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"TRUE",
"Description"=>""
},
{
"AttributeName"=>"DHE-RSA-AES128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-EDH-DSS-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-KRB5-RC2-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-RSA-AES256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"KRB5-DES-CBC3-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"RC4-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"TRUE",
"Description"=>""
},
{
"AttributeName"=>"EXP-RC2-CBC-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-ADH-RC4-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-RC4-MD5",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"CAMELLIA256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-DSS-CAMELLIA128-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-KRB5-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"EXP-ADH-DES-CBC-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"DHE-DSS-AES256-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
},
{
"AttributeName"=>"ADH-SEED-SHA",
"AttributeType"=>"Boolean",
"Cardinality"=>"ONE",
"DefaultValue"=>"FALSE",
"Description"=>""
}],
"PolicyTypeName"=>"SSLNegotiationPolicyType"
}]
end

134
lib/fog/aws/emr.rb Normal file
View file

@ -0,0 +1,134 @@
require 'fog/aws'
module Fog
module AWS
class EMR < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
class IdentifierTaken < Fog::Errors::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
request_path 'fog/aws/requests/emr'
request :add_instance_groups
request :add_job_flow_steps
request :describe_job_flows
request :modify_instance_groups
request :run_job_flow
request :set_termination_protection
request :terminate_job_flows
# model_path 'fog/aws/models/rds'
# model :server
# collection :servers
# model :snapshot
# collection :snapshots
# model :parameter_group
# collection :parameter_groups
#
# model :parameter
# collection :parameters
#
# model :security_group
# collection :security_groups
class Mock
def initialize(options={})
Fog::Mock.not_implemented
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to EMR
#
# ==== Notes
# options parameter must include values for :aws_access_key_id and
# :aws_secret_access_key in order to create a connection
#
# ==== Examples
# emr = EMR.new(
# :aws_access_key_id => your_aws_access_key_id,
# :aws_secret_access_key => your_aws_secret_access_key
# )
#
# ==== Parameters
# * options<~Hash> - config arguments for connection. Defaults to {}.
# * region<~String> - optional region to use. For instance, in 'eu-west-1', 'us-east-1' and etc.
#
# ==== Returns
# * EMR object with connection to AWS.
def initialize(options={})
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
options[:region] ||= 'us-east-1'
@host = options[:host] || "elasticmapreduce.#{options[:region]}.amazonaws.com"
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || 443
@scheme = options[:scheme] || 'https'
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", @persistent, @connection_options)
@region = options[:region]
end
def reload
@connection.reset
end
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
refresh_credentials_if_expired
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
body = Fog::AWS.signed_params(
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,
:port => @port,
:version => '2009-03-31' #'2010-07-28'
}
)
begin
response = @connection.request({
:body => body,
:expects => 200,
:headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
:idempotent => idempotent,
:host => @host,
:method => 'POST',
:parser => parser
})
rescue Excon::Errors::HTTPStatusError
raise
end
response
end
end
end
end
end

172
lib/fog/aws/glacier.rb Normal file
View file

@ -0,0 +1,172 @@
require 'fog/aws'
module Fog
module AWS
class Glacier < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
requires :aws_access_key_id, :aws_secret_access_key
recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
request_path 'fog/aws/requests/glacier'
request :abort_multipart_upload
request :complete_multipart_upload
request :create_archive
request :create_vault
request :delete_archive
request :delete_vault
request :delete_vault_notification_configuration
request :describe_job
request :describe_vault
request :get_job_output
request :get_vault_notification_configuration
request :initiate_job
request :initiate_multipart_upload
request :list_jobs
request :list_multipart_uploads
request :list_parts
request :list_vaults
request :set_vault_notification_configuration
request :upload_part
model_path 'fog/aws/models/glacier'
model :vault
collection :vaults
MEGABYTE = 1024*1024
class TreeHash
def self.digest(body)
new.add_part(body)
end
def reduce_digests(digests)
while digests.length > 1
digests = digests.each_slice(2).collect do |pair|
if pair.length == 2
Digest::SHA256.digest(pair[0]+pair[1])
else
pair.first
end
end
end
digests.first
end
def initialize
@digests = []
end
def add_part(bytes)
part = self.digest_for_part(bytes)
@digests << part
part.unpack('H*').first
end
def digest_for_part(body)
chunk_count = [body.bytesize / MEGABYTE + (body.bytesize % MEGABYTE > 0 ? 1 : 0), 1].max
if body.respond_to? :byteslice
digests_for_part = chunk_count.times.collect {|chunk_index| Digest::SHA256.digest(body.byteslice(chunk_index * MEGABYTE, MEGABYTE))}
else
if body.respond_to? :encoding
old_encoding = body.encoding
body.force_encoding('BINARY')
end
digests_for_part = chunk_count.times.collect {|chunk_index| Digest::SHA256.digest(body.slice(chunk_index * MEGABYTE, MEGABYTE))}
if body.respond_to? :encoding
body.force_encoding(old_encoding)
end
end
reduce_digests(digests_for_part)
end
def hexdigest
digest.unpack('H*').first
end
def digest
reduce_digests(@digests)
end
end
class Mock
def initialize(options={})
Fog::Mock.not_implemented
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to Glacier
#
# ==== Notes
# options parameter must include values for :aws_access_key_id and
# :aws_secret_access_key in order to create a connection
#
# ==== Examples
# ses = SES.new(
# :aws_access_key_id => your_aws_access_key_id,
# :aws_secret_access_key => your_aws_secret_access_key
# )
#
# ==== Parameters
# * options<~Hash> - config arguments for connection. Defaults to {}.
# * region<~String> - optional region to use. For instance, 'us-east-1' and etc.
#
# ==== Returns
# * Glacier object with connection to AWS.
def initialize(options={})
@use_iam_profile = options[:use_iam_profile]
@region = options[:region] || 'us-east-1'
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@host = options[:host] || "glacier.#{@region}.amazonaws.com"
@version = '2012-06-01'
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@port = options[:port] || 443
@scheme = options[:scheme] || 'https'
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", @persistent, @connection_options)
end
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@signer = Fog::AWS::SignatureV4.new( @aws_access_key_id, @aws_secret_access_key,@region,'glacier')
end
def request(params, &block)
refresh_credentials_if_expired
date = Fog::Time.now
params[:headers]['Date'] = date.to_date_header
params[:headers]['x-amz-date'] = date.to_iso8601_basic
params[:headers]['Host'] = @host
params[:headers]['x-amz-glacier-version'] = @version
params[:headers]['x-amz-security-token'] = @aws_session_token if @aws_session_token
params[:headers]['Authorization'] = @signer.sign params, date
response = @connection.request(params, &block)
if response.headers['Content-Type'] == 'application/json' && response.body.size > 0 #body will be empty if the streaming form has been used
response.body = Fog::JSON.decode(response.body)
end
response
end
end
end
end
end

View file

@ -1,4 +1,4 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws'))
require 'fog/aws'
module Fog
module AWS
@ -11,28 +11,37 @@ module Fog
class ValidationError < Fog::AWS::IAM::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :host, :path, :port, :scheme, :persistent
recognizes :host, :path, :port, :scheme, :persistent, :instrumentor, :instrumentor_name
request_path 'fog/aws/requests/iam'
request :add_user_to_group
request :add_role_to_instance_profile
request :create_access_key
request :create_account_alias
request :create_group
request :create_instance_profile
request :create_login_profile
request :create_role
request :create_user
request :delete_access_key
request :delete_account_alias
request :delete_group
request :delete_group_policy
request :delete_instance_profile
request :delete_login_profile
request :delete_role
request :delete_role_policy
request :delete_server_certificate
request :delete_signing_certificate
request :delete_user
request :delete_user_policy
request :get_group
request :get_group_policy
request :get_instance_profile
request :get_role_policy
request :get_login_profile
request :get_server_certificate
request :get_role
request :get_user
request :get_user_policy
request :list_access_keys
@ -40,12 +49,18 @@ module Fog
request :list_group_policies
request :list_groups
request :list_groups_for_user
request :list_instance_profiles
request :list_instance_profiles_for_role
request :list_roles
request :list_role_policies
request :list_server_certificates
request :list_signing_certificates
request :list_user_policies
request :list_users
request :put_group_policy
request :put_role_policy
request :put_user_policy
request :remove_role_from_instance_profile
request :remove_user_from_group
request :update_access_key
request :update_group
@ -56,12 +71,37 @@ module Fog
request :upload_server_certificate
request :upload_signing_certificate
model_path 'fog/aws/models/iam'
model :user
collection :users
model :policy
collection :policies
model :access_key
collection :access_keys
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {
:owner_id => Fog::AWS::Mock.owner_id,
:server_certificates => {}
:server_certificates => {},
:users => Hash.new do |uhash, ukey|
uhash[ukey] = {
:user_id => Fog::AWS::Mock.key_id,
:path => '/',
:arn => "arn:aws:iam::#{Fog::AWS::Mock.owner_id}:user/#{ukey}",
:access_keys => [],
:policies => {}
}
end,
:groups => Hash.new do |ghash, gkey|
ghash[gkey] = {
:group_id => Fog::AWS::Mock.key_id,
:arn => "arn:aws:iam::#{Fog::AWS::Mock.owner_id}:group/#{gkey}",
:members => []
}
end
}
end
end
@ -108,11 +148,12 @@ module Fog
# * IAM object with connection to AWS.
def initialize(options={})
require 'fog/core/parser'
require 'multi_json'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@connection_options = options[:connection_options] || {}
@instrumentor = options[:instrumentor]
@instrumentor_name = options[:instrumentor_name] || 'fog.aws.iam'
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@host = options[:host] || 'iam.amazonaws.com'
@path = options[:path] || '/'
@ -144,35 +185,39 @@ module Fog
}
)
begin
response = @connection.request({
:body => body,
:expects => 200,
:idempotent => idempotent,
:headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
:host => @host,
:method => 'POST',
:parser => parser
})
if @instrumentor
@instrumentor.instrument("#{@instrumentor_name}.request", params) do
_request(body, idempotent, parser)
end
else
_request(body, idempotent, parser)
end
end
response
rescue Excon::Errors::HTTPStatusError => error
if match = error.message.match(/<Code>(.*)<\/Code>(?:.*<Message>(.*)<\/Message>)?/m)
case match[1]
when 'CertificateNotFound', 'NoSuchEntity'
raise Fog::AWS::IAM::NotFound.slurp(error, match[2])
when 'EntityAlreadyExists', 'KeyPairMismatch', 'LimitExceeded', 'MalformedCertificate', 'ValidationError'
raise Fog::AWS::IAM.const_get(match[1]).slurp(error, match[2])
else
raise Fog::AWS::IAM::Error.slurp(error, "#{match[1]} => #{match[2]}") if match[1]
raise
end
def _request(body, idempotent, parser)
@connection.request({
:body => body,
:expects => 200,
:idempotent => idempotent,
:headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
:host => @host,
:method => 'POST',
:parser => parser
})
rescue Excon::Errors::HTTPStatusError => error
if match = error.message.match(/<Code>(.*)<\/Code>(?:.*<Message>(.*)<\/Message>)?/m)
case match[1]
when 'CertificateNotFound', 'NoSuchEntity'
raise Fog::AWS::IAM::NotFound.slurp(error, match[2])
when 'EntityAlreadyExists', 'KeyPairMismatch', 'LimitExceeded', 'MalformedCertificate', 'ValidationError'
raise Fog::AWS::IAM.const_get(match[1]).slurp(error, match[2])
else
raise Fog::AWS::IAM::Error.slurp(error, "#{match[1]} => #{match[2]}") if match[1]
raise
end
else
raise
end
end
end

View file

@ -11,7 +11,7 @@ module Fog
data = []
next_token = nil
loop do
result = connection.describe_scaling_activities('NextToken' => next_token).body['DescribeScalingActivitiesResult']
result = service.describe_scaling_activities('NextToken' => next_token).body['DescribeScalingActivitiesResult']
data += result['Activities']
next_token = result['NextToken']
break if next_token.nil?
@ -20,7 +20,7 @@ module Fog
end
def get(identity)
data = connection.describe_scaling_activities('ActivityId' => identity).body['DescribeScalingActivitiesResult']['Activities'].first
data = service.describe_scaling_activities('ActivityId' => identity).body['DescribeScalingActivitiesResult']['Activities'].first
new(data) unless data.nil?
end

View file

@ -9,14 +9,14 @@ module Fog
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
attribute :cause, :aliases => 'Cause'
attribute :description, :aliases => 'Description'
attribute :end_time, :aliases => 'EndTime'
attribute :end_time, :aliases => 'EndTime'
attribute :progress, :aliases => 'Progress'
attribute :start_time, :aliases => 'StartTime'
attribute :status_code, :aliases => 'StatusCode'
attribute :status_message, :aliases => 'StatusMessage'
def group
connection.groups.get(attributes['AutoScalingGroupName'])
service.groups.get(attributes['AutoScalingGroupName'])
end
def save

View file

@ -39,7 +39,9 @@ module Fog
requires :image_id
requires :instance_type
connection.create_launch_configuration(image_id, instance_type, id) #, listeners.map{|l| l.to_params})
options = Hash[self.class.aliases.map { |key, value| [key, send(value)] }]
options.delete_if { |key, value| value.nil? }
service.create_launch_configuration(image_id, instance_type, id, options) #, listeners.map{|l| l.to_params})
# reload instead of merge attributes b/c some attrs (like HealthCheck)
# may be set, but only the DNS name is returned in the create_load_balance
@ -54,7 +56,7 @@ module Fog
def destroy
requires :id
connection.delete_launch_configuration(id)
service.delete_launch_configuration(id)
end
end

View file

@ -16,7 +16,7 @@ module Fog
data = []
next_token = nil
loop do
result = connection.describe_launch_configurations('NextToken' => next_token).body['DescribeLaunchConfigurationsResult']
result = service.describe_launch_configurations('NextToken' => next_token).body['DescribeLaunchConfigurationsResult']
data += result['LaunchConfigurations']
next_token = result['NextToken']
break if next_token.nil?
@ -25,7 +25,7 @@ module Fog
end
def get(identity)
data = connection.describe_launch_configurations('LaunchConfigurationNames' => identity).body['DescribeLaunchConfigurationsResult']['LaunchConfigurations'].first
data = service.describe_launch_configurations('LaunchConfigurationNames' => identity).body['DescribeLaunchConfigurationsResult']['LaunchConfigurations'].first
new(data) unless data.nil?
end

View file

@ -4,7 +4,6 @@ module Fog
module AWS
class AutoScaling
class Group < Fog::Model
identity :id, :aliases => 'AutoScalingGroupName'
attribute :arn, :aliases => 'AutoScalingGroupARN'
attribute :availability_zones, :aliases => 'AvailabilityZones'
@ -21,10 +20,12 @@ module Fog
attribute :min_size, :aliases => 'MinSize'
attribute :placement_group, :aliases => 'PlacementGroup'
attribute :suspended_processes, :aliases => 'SuspendedProcesses'
attribute :tags, :aliases => 'Tags'
attribute :termination_policies, :aliases => 'TerminationPolicies'
attribute :vpc_zone_identifier, :aliases => 'VPCZoneIdentifier'
def initialize(attributes={})
attributes['DefaultCooldown'] ||= 0
attributes['DefaultCooldown'] ||= 300
attributes['DesiredCapacity'] ||= 0
attributes['EnabledMetrics'] ||= []
attributes['HealthCheckGracePeriod'] ||= 0
@ -34,6 +35,8 @@ module Fog
attributes['MaxSize'] ||= 0
attributes['MinSize'] ||= 0
attributes['SuspendedProcesses'] ||= []
attributes['Tags'] ||= []
attributes['TerminationPolicies'] ||= ['Default']
super
end
@ -42,40 +45,37 @@ module Fog
data = []
next_token = nil
loop do
result = connection.describe_scaling_activities('AutoScalingGroupName' => id, 'NextToken' => next_token).body['DescribeScalingActivitiesResult']
result = service.describe_scaling_activities('AutoScalingGroupName' => id, 'NextToken' => next_token).body['DescribeScalingActivitiesResult']
data += result['Activities']
next_token = result['NextToken']
break if next_token.nil?
end
Fog::AWS::AutoScaling::Activities.new({
:data => data,
:connection => connection,
:service => service,
#:load_balancer => self
})
end
def configuration
requires :launch_configuration_name
connection.configurations.get(launch_configuration_name)
service.configurations.get(launch_configuration_name)
end
def disable_metrics_collection(metrics = {})
requires :id
connection.disable_metrics_collection(id, 'Metrics' => metrics)
service.disable_metrics_collection(id, 'Metrics' => metrics)
reload
end
def enable_metrics_collection(metrics = {})
def enable_metrics_collection(granularity = '1Minute', metrics = {})
requires :id
connection.enable_metrics_collection(id, 'Metrics' => metrics)
service.enable_metrics_collection(id, granularity, 'Metrics' => metrics)
reload
end
def instances
Fog::AWS::AutoScaling::Instances.new({
:data => attributes['Instances'],
:connection => connection
})
Fog::AWS::AutoScaling::Instances.new(:service => service).load(attributes[:instances])
end
def instances_in_service
@ -88,13 +88,13 @@ module Fog
def resume_processes(processes = [])
requires :id
connection.resume_processes(id, 'ScalingProcesses' => processes)
service.resume_processes(id, 'ScalingProcesses' => processes)
reload
end
def suspend_processes(processes = [])
requires :id
connection.suspend_processes(id, 'ScalingProcesses' => processes)
service.suspend_processes(id, 'ScalingProcesses' => processes)
reload
end
@ -112,7 +112,7 @@ module Fog
requires :max_size
requires :min_size
connection.create_auto_scaling_group(id, availability_zones, launch_configuration_name, max_size, min_size)
service.create_auto_scaling_group(id, availability_zones, launch_configuration_name, max_size, min_size, options)
reload
end
@ -121,19 +121,26 @@ module Fog
# self
#end
def destroy
def destroy(options = { :force => false })
requires :id
connection.delete_auto_scaling_group(id)
opts = {}
opts.merge!({'ForceDelete' => true}) if options[:force]
service.delete_auto_scaling_group(id, opts)
end
private
def update(options)
def update
requires :id
connection.update_auto_scaling_group(id, options)
service.update_auto_scaling_group(id, options)
reload
end
def options
ret = Hash[self.class.aliases.map { |key, value| [key, send(value)] }]
ret.delete_if { |key, value| value.nil? }
ret
end
end
end
end

View file

@ -16,7 +16,7 @@ module Fog
data = []
next_token = nil
loop do
result = connection.describe_auto_scaling_groups('NextToken' => next_token).body['DescribeAutoScalingGroupsResult']
result = service.describe_auto_scaling_groups('NextToken' => next_token).body['DescribeAutoScalingGroupsResult']
data += result['AutoScalingGroups']
next_token = result['NextToken']
break if next_token.nil?
@ -25,7 +25,7 @@ module Fog
end
def get(identity)
data = connection.describe_auto_scaling_groups('AutoScalingGroupNames' => identity).body['DescribeAutoScalingGroupsResult']['AutoScalingGroups'].first
data = service.describe_auto_scaling_groups('AutoScalingGroupNames' => identity).body['DescribeAutoScalingGroupsResult']['AutoScalingGroups'].first
new(data) unless data.nil?
end

View file

@ -17,22 +17,22 @@ module Fog
end
def group
connection.groups.get(attributes['AutoScalingGroupName'])
service.groups.get(attributes['AutoScalingGroupName'])
end
def configuration
connection.configurations.get(attributes['LaunchConfigurationName'])
service.configurations.get(attributes['LaunchConfigurationName'])
end
def set_health(health_status, options)
requires :id
connection.set_instance_health(health_status, id, options)
service.set_instance_health(health_status, id, options)
reload
end
def terminate(should_decrement_desired_capacity)
requires :id
connection.terminate_instance_in_auto_scaling_group(id, should_decrement_desired_capacity)
service.terminate_instance_in_auto_scaling_group(id, should_decrement_desired_capacity)
reload
end
@ -51,7 +51,7 @@ module Fog
#def destroy
# requires :id
# connection.delete_auto_scaling_group(id)
# service.delete_auto_scaling_group(id)
#end
end

View file

@ -11,7 +11,7 @@ module Fog
data = []
next_token = nil
loop do
result = connection.describe_auto_scaling_instances('NextToken' => next_token).body['DescribeAutoScalingInstancesResult']
result = service.describe_auto_scaling_instances('NextToken' => next_token).body['DescribeAutoScalingInstancesResult']
data += result['AutoScalingInstances']
next_token = result['NextToken']
break if next_token.nil?
@ -20,7 +20,7 @@ module Fog
end
def get(identity)
data = connection.describe_auto_scaling_instances('InstanceIds' => identity).body['DescribeAutoScalingInstancesResult']['AutoScalingInstances'].first
data = service.describe_auto_scaling_instances('InstanceIds' => identity).body['DescribeAutoScalingInstancesResult']['AutoScalingInstances'].first
new(data) unless data.nil?
end

View file

@ -0,0 +1,33 @@
require 'fog/aws/models/auto_scaling/policy'
module Fog
module AWS
class AutoScaling
class Policies < Fog::Collection
model Fog::AWS::AutoScaling::Policy
# Creates a new scaling policy.
def initialize(attributes={})
super
end
def all
data = []
next_token = nil
loop do
result = service.describe_policies('NextToken' => next_token).body['DescribePoliciesResult']
data += result['ScalingPolicies']
next_token = result['NextToken']
break if next_token.nil?
end
load(data)
end
def get(identity, auto_scaling_group = nil)
data = service.describe_policies('PolicyNames' => identity, 'AutoScalingGroupName' => auto_scaling_group).body['DescribePoliciesResult']['ScalingPolicies'].first
new(data) unless data.nil?
end
end
end
end
end

View file

@ -0,0 +1,46 @@
require 'fog/core/model'
module Fog
module AWS
class AutoScaling
class Policy < Fog::Model
identity :id, :aliases => 'PolicyName'
attribute :arn, :aliases => 'PolicyARN'
attribute :adjustment_type, :aliases => 'AdjustmentType'
attribute :alarms, :aliases => 'Alarms'
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
attribute :cooldown, :aliases => 'Cooldown'
attribute :min_adjustment_step, :aliases => 'MinAdjustmentStep'
attribute :scaling_adjustment, :aliases => 'ScalingAdjustment'
def initialize(attributes)
attributes['AdjustmentType'] ||= 'ChangeInCapacity'
attributes['ScalingAdjustment'] ||= 1
super
end
# TODO: implement #alarms
# TODO: implement #auto_scaling_group
def save
requires :id
requires :adjustment_type
requires :auto_scaling_group_name
requires :scaling_adjustment
options = Hash[self.class.aliases.map { |key, value| [key, send(value)] }]
options.delete_if { |key, value| value.nil? }
service.put_scaling_policy(adjustment_type, auto_scaling_group_name, id, scaling_adjustment, options)
reload
end
def destroy
requires :id
requires :auto_scaling_group_name
service.delete_policy(auto_scaling_group_name, id)
end
end
end
end
end

View file

@ -0,0 +1,62 @@
require 'fog/core/model'
module Fog
module AWS
class ElasticBeanstalk
class Application < Fog::Model
identity :name, :aliases => 'ApplicationName'
attribute :template_names, :aliases => 'ConfigurationTemplates'
attribute :created_at, :aliases => 'DateCreated'
attribute :updated_at, :aliases => 'DateUpdated'
attribute :description, :aliases => 'Description'
attribute :version_names, :aliases => 'Versions'
def initialize(attributes={})
super
end
def environments
requires :name
service.environments.all({'ApplicationName' => name})
end
def events
requires :name
service.events.all({'ApplicationName' => name})
end
def templates
requires :name
service.templates.all({'ApplicationName' => name})
end
def versions
requires :name
service.versions.all({'ApplicationName' => name})
end
def destroy
requires :name
service.delete_application(name)
true
end
def save
requires :name
options = {
'ApplicationName' => name
}
options['Description'] = description unless description.nil?
data = service.create_application(options).body['CreateApplicationResult']['Application']
merge_attributes(data)
true
end
end
end
end
end

View file

@ -0,0 +1,25 @@
require 'fog/core/collection'
require 'fog/aws/models/beanstalk/application'
module Fog
module AWS
class ElasticBeanstalk
class Applications < Fog::Collection
model Fog::AWS::ElasticBeanstalk::Application
def all(application_names=[])
data = service.describe_applications(application_names).body['DescribeApplicationsResult']['Applications']
load(data) # data is an array of attribute hashes
end
def get(application_name)
if data = service.describe_applications([application_name]).body['DescribeApplicationsResult']['Applications'].first
new(data)
end
end
end
end
end
end

View file

@ -0,0 +1,145 @@
require 'fog/core/model'
module Fog
module AWS
class ElasticBeanstalk
class Environment < Fog::Model
identity :name, :aliases => 'EnvironmentName'
attribute :id, :aliases => 'EnvironmentId'
attribute :application_name, :aliases => 'ApplicationName'
attribute :cname, :aliases => 'CNAME'
attribute :cname_prefix, :aliases => 'CNAMEPrefix'
attribute :created_at, :aliases => 'DateCreated'
attribute :updated_at, :aliases => 'DateUpdated'
attribute :updated_at, :aliases => 'DateUpdated'
attribute :description, :aliases => 'Description'
attribute :endpoint_url, :aliases => 'EndpointURL'
attribute :health, :aliases => 'Health'
attribute :resources, :aliases => 'Resources'
attribute :solution_stack_name, :aliases => 'SolutionStackName'
attribute :status, :aliases => 'Status'
attribute :template_name, :aliases => 'TemplateName'
attribute :version_label, :aliases => 'VersionLabel'
attribute :option_settings, :aliases => 'OptionSettings'
attribute :options_to_remove, :aliases => 'OptionsToRemove'
def healthy?
health == 'Green'
end
def ready?
status == 'Ready'
end
def terminated?
status == 'Terminated'
end
# Returns the current live resources for this environment
def live_resources
requires :id
data = service.describe_environment_resources({'EnvironmentId' => id}).body['DescribeEnvironmentResourcesResult']['EnvironmentResources']
data.delete('EnvironmentName') # Delete the environment name from the result, only return actual resources
data
end
# Returns the load balancer object associated with the environment.
def load_balancer(elb_connection = Fog::AWS[:elb])
requires :resources
elb_connection.load_balancers.get(resources['LoadBalancer']['LoadBalancerName'])
end
# Return events related to this version
def events
requires :id
service.events.all({'EnvironmentId' => id})
end
# Restarts the app servers in this environment
def restart_app_server
requires :id
service.restart_app_server({'EnvironmentId' => id})
reload
end
# Rebuilds the environment
def rebuild
requires :id
service.rebuild_environment({'EnvironmentId' => id})
reload
end
def swap_cnames(source)
requires :name
service.swap_environment_cnames({
'SourceEnvironmentName' => source.name,
'DestinationEnvironmentName' => name
})
source.reload
reload
end
# Return the version object for this environment
def version
requires :application_name, :version_label
service.versions.get(application_name, version_label)
end
# Update the running version of this environment
def version=(new_version)
requires :id
if new_version.is_a?(String)
new_version_label = new_version
elsif new_version.is_a?(Fog::AWS::ElasticBeanstalk::Version)
new_version_label = new_version.label
else
raise "Unknown type for new_version, must be either String or Fog::AWS::ElasticBeanstalk::Version"
end
if new_version.nil?
raise "Version label not specified."
end
data = service.update_environment({
'EnvironmentId' => id,
'VersionLabel' => new_version_label
}).body['UpdateEnvironmentResult']
merge_attributes(data)
end
def destroy
requires :id
service.terminate_environment({'EnvironmentId' => id})
true
end
def save
requires :name, :application_name
requires_one :template_name, :solution_stack_name
options = {
'ApplicationName' => application_name,
'CNAMEPrefix' => cname_prefix,
'Description' => description,
'EnvironmentName' => name,
'OptionSettings' => option_settings,
'OptionsToRemove' => options_to_remove,
'SolutionStackName' => solution_stack_name,
'TemplateName' => template_name,
'VersionLabel' => version_label
}
options.delete_if {|key, value| value.nil?}
data = service.create_environment(options).body['CreateEnvironmentResult']
merge_attributes(data)
true
end
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'fog/core/collection'
require 'fog/aws/models/beanstalk/environment'
module Fog
module AWS
class ElasticBeanstalk
class Environments < Fog::Collection
model Fog::AWS::ElasticBeanstalk::Environment
def all(options={})
data = service.describe_environments(options).body['DescribeEnvironmentsResult']['Environments']
load(data) # data is an array of attribute hashes
end
# Gets an environment given a name.
#
def get(environment_name)
options = { 'EnvironmentNames' => [environment_name] }
if data = service.describe_environments(options).body['DescribeEnvironmentsResult']['Environments'].first
new(data)
end
end
end
end
end
end

View file

@ -0,0 +1,20 @@
require 'fog/core/model'
module Fog
module AWS
class ElasticBeanstalk
class Event < Fog::Model
attribute :application_name, :aliases => 'ApplicationName'
attribute :environment_name, :aliases => 'EnvironmentName'
attribute :date, :aliases => 'EventDate'
attribute :message, :aliases => 'Message'
attribute :request_id, :aliases => 'RequestId'
attribute :severity, :aliases => 'Severity'
attribute :template_name, :aliases => 'TemplateName'
attribute :version_label, :aliases => 'VersionLabel'
end
end
end
end

View file

@ -0,0 +1,19 @@
require 'fog/core/collection'
require 'fog/aws/models/beanstalk/event'
module Fog
module AWS
class ElasticBeanstalk
class Events < Fog::Collection
model Fog::AWS::ElasticBeanstalk::Event
def all(options={})
data = service.describe_events(options).body['DescribeEventsResult']['Events']
load(data) # data is an array of attribute hashes
end
end
end
end
end

View file

@ -0,0 +1,78 @@
require 'fog/core/model'
module Fog
module AWS
class ElasticBeanstalk
class Template < Fog::Model
attribute :name, :aliases => 'TemplateName'
attribute :application_name, :aliases => 'ApplicationName'
attribute :created_at, :aliases => 'DateCreated'
attribute :updated_at, :aliases => 'DateUpdated'
attribute :deployment_status, :aliases => 'DeploymentStatus'
attribute :description, :aliases => 'Description'
attribute :environment_id
attribute :environment_name, :aliases => 'EnvironmentName'
attribute :solution_stack_name, :aliases => 'SolutionStackName'
attribute :source_configuration
attribute :option_settings, :aliases => 'OptionSettings'
def initialize(attributes={})
super
end
# Returns an array of options that may be set on this template
def options
requires :name, :application_name
data = service.describe_configuration_options({
'ApplicationName' => application_name,
'TemplateName' => name
})
data.body['DescribeConfigurationOptionsResult']['Options']
end
def destroy
requires :name, :application_name
service.delete_configuration_template(application_name, name)
true
end
def save
requires :name, :application_name
options = {
'ApplicationName' => application_name,
'Description' => description,
'EnvironmentId' => environment_id,
'OptionSettings' => option_settings,
'SolutionStackName' => solution_stack_name,
'SourceConfiguration' => source_configuration,
'TemplateName' => name
}
options.delete_if {|key, value| value.nil?}
data = service.create_configuration_template(options).body['CreateConfigurationTemplateResult']
merge_attributes(data)
true
end
def modify(new_attributes)
requires :name, :application_name
options = {
'ApplicationName' => application_name,
'Description' => new_attributes[:description],
'OptionSettings' => new_attributes[:option_settings],
'TemplateName' => name
}
options.delete_if {|key, value| value.nil?}
data = service.update_configuration_template(options).body['UpdateConfigurationTemplateResult']
merge_attributes(data)
true
end
end
end
end
end

View file

@ -0,0 +1,70 @@
require 'fog/core/collection'
require 'fog/aws/models/beanstalk/template'
module Fog
module AWS
class ElasticBeanstalk
class Templates < Fog::Collection
model Fog::AWS::ElasticBeanstalk::Template
# Describes all configuration templates, may optionally pass an ApplicationName filter
#
# Note: This is currently an expensive operation requiring multiple API calls due to a lack of
# a describe configuration templates call in the AWS API.
def all(options={})
application_filter = []
if options.has_key?('ApplicationName')
application_filter << options['ApplicationName']
end
# Initialize with empty array
data = []
applications = service.describe_applications(application_filter).body['DescribeApplicationsResult']['Applications']
applications.each { |application|
application['ConfigurationTemplates'].each { |template_name|
begin
options = {
'ApplicationName' => application['ApplicationName'],
'TemplateName' => template_name
}
settings = service.describe_configuration_settings(options).body['DescribeConfigurationSettingsResult']['ConfigurationSettings']
if settings.length == 1
# Add to data
data << settings.first
end
rescue Fog::AWS::ElasticBeanstalk::InvalidParameterError
# Ignore
end
}
}
load(data) # data is an array of attribute hashes
end
def get(application_name, template_name)
options = {
'ApplicationName' => application_name,
'TemplateName' => template_name
}
result = nil
# There is no describe call for templates, so we must use describe_configuration_settings. Unfortunately,
# it throws an exception if template name doesn't exist, which is inconsistent, catch and return nil
begin
data = service.describe_configuration_settings(options).body['DescribeConfigurationSettingsResult']['ConfigurationSettings']
if data.length == 1
result = new(data.first)
end
rescue Fog::AWS::ElasticBeanstalk::InvalidParameterError
end
result
end
end
end
end
end

View file

@ -0,0 +1,79 @@
require 'fog/core/model'
module Fog
module AWS
class ElasticBeanstalk
class Version < Fog::Model
attribute :label, :aliases => 'VersionLabel'
attribute :application_name, :aliases => 'ApplicationName'
attribute :created_at, :aliases => 'DateCreated'
attribute :updated_at, :aliases => 'DateUpdated'
attribute :description, :aliases => 'Description'
attribute :source_bundle, :aliases => 'SourceBundle'
attribute :auto_create_application # FIXME - should be write only
def initialize(attributes={})
super
end
# Return events related to this version
def events
requires :label, :application_name
service.events.all({
'ApplicationName' => application_name,
'VersionLabel' => label
})
end
# Returns environments running this version
def environments
requires :label, :application_name
service.environments.all({
'ApplicationName' => application_name,
'VersionLabel' => label
})
end
def destroy(delete_source_bundle = nil)
requires :label, :application_name
service.delete_application_version(application_name, label, delete_source_bundle)
true
end
def save
requires :label, :application_name
options = {
'ApplicationName' => application_name,
'AutoCreateApplication' => auto_create_application,
'Description' => description,
'SourceBundle' => source_bundle,
'VersionLabel' => label
}
options.delete_if {|key, value| value.nil?}
data = service.create_application_version(options).body['CreateApplicationVersionResult']['ApplicationVersion']
merge_attributes(data)
true
end
# Updates the version label with the current property values. Currently only updates description
def update
requires :label, :application_name
options = {
'ApplicationName' => application_name,
'Description' => description,
'VersionLabel' => label
}
options.delete_if {|key, value| value.nil?}
data = service.update_application_version(options).body['UpdateApplicationVersionResult']['ApplicationVersion']
merge_attributes(data)
end
end
end
end
end

View file

@ -0,0 +1,31 @@
require 'fog/core/collection'
require 'fog/aws/models/beanstalk/version'
module Fog
module AWS
class ElasticBeanstalk
class Versions < Fog::Collection
model Fog::AWS::ElasticBeanstalk::Version
def all(options={})
data = service.describe_application_versions(options).body['DescribeApplicationVersionsResult']['ApplicationVersions']
load(data) # data is an array of attribute hashes
end
def get(application_name, version_label)
if data = service.describe_application_versions({
'ApplicationName' => application_name,
'VersionLabels' => [version_label]
}).body['DescribeApplicationVersionsResult']['ApplicationVersions']
if data.length == 1
new(data.first)
end
end
end
end
end
end
end

View file

@ -0,0 +1,93 @@
require 'fog/core/model'
require 'fog/aws/models/cdn/invalidations'
require 'fog/aws/models/cdn/distribution_helper'
module Fog
module CDN
class AWS
class Distribution < Fog::Model
include Fog::CDN::AWS::DistributionHelper
identity :id, :aliases => 'Id'
attribute :caller_reference, :aliases => 'CallerReference'
attribute :last_modified_time, :aliases => 'LastModifiedTime'
attribute :status, :aliases => 'Status'
attribute :s3_origin, :aliases => 'S3Origin'
attribute :custom_origin, :aliases => 'CustomOrigin'
attribute :cname, :aliases => 'CNAME'
attribute :comment, :aliases => 'Comment'
attribute :enabled, :aliases => 'Enabled'
attribute :in_progress_invalidation_batches, :aliases => 'InProgressInvalidationBatches'
attribute :logging, :aliases => 'Logging'
attribute :trusted_signers, :aliases => 'TrustedSigners'
attribute :default_root_object,:aliases => 'DefaultRootObject'
attribute :domain, :aliases => 'DomainName'
attribute :etag, :aliases => ['Etag', 'ETag']
# items part of DistributionConfig
CONFIG = [ :caller_reference, :origin, :cname, :comment, :enabled, :logging, :trusted_signers, :default_root_object ]
def initialize(new_attributes = {})
super(distribution_config_to_attributes(new_attributes))
end
def invalidations
@invalidations ||= begin
Fog::CDN::AWS::Invalidations.new(
:distribution => self,
:service => service
)
end
end
def save
requires_one :s3_origin, :custom_origin
options = attributes_to_options
response = identity ? put_distribution_config(identity, etag, options) : post_distribution(options)
etag = response.headers['ETag']
merge_attributes(response.body)
true
end
private
def delete_distribution(identity, etag)
service.delete_distribution(identity, etag)
end
def put_distribution_config(identity, etag, options)
service.put_distribution_config(identity, etag, options)
end
def post_distribution(options = {})
service.post_distribution(options)
end
def attributes_to_options
options = {
'CallerReference' => caller_reference,
'S3Origin' => s3_origin,
'CustomOrigin' => custom_origin,
'CNAME' => cname,
'Comment' => comment,
'Enabled' => enabled,
'Logging' => logging,
'TrustedSigners' => trusted_signers,
'DefaultRootObject' => default_root_object
}
options.reject! { |k,v| v.nil? }
options.reject! { |k,v| v.respond_to?(:empty?) && v.empty? }
options
end
def distribution_config_to_attributes(new_attributes = {})
new_attributes.merge(new_attributes.delete('DistributionConfig') || {})
end
end
end
end
end

View file

@ -0,0 +1,64 @@
require 'fog/core/collection'
module Fog
module CDN
class AWS
module DistributionHelper
def destroy
requires :identity, :etag, :caller_reference
raise "Distribution must be disabled to be deleted" unless disabled?
delete_distribution(identity, etag)
true
end
def enabled?
requires :identity
!!enabled and ready?
end
def disabled?
requires :identity
not enabled? and ready?
end
def custom_origin?
requires :identity
not custom_origin.nil?
end
def ready?
requires :identity
status == 'Deployed'
end
def enable
requires :identity
reload if etag.nil? or caller_reference.nil?
unless enabled?
self.enabled = true
response = put_distribution_config(identity, etag, attributes_to_options)
etag = response.headers['ETag']
merge_attributes(response.body)
end
true
end
def disable
requires :identity
reload if etag.nil? or caller_reference.nil?
if enabled?
self.enabled = false
response = put_distribution_config(identity, etag, attributes_to_options)
etag = response.headers['ETag']
merge_attributes(response.body)
end
true
end
end
end
end
end

View file

@ -0,0 +1,32 @@
require 'fog/core/collection'
require 'fog/aws/models/cdn/distribution'
require 'fog/aws/models/cdn/distributions_helper'
module Fog
module CDN
class AWS
class Distributions < Fog::Collection
include Fog::CDN::AWS::DistributionsHelper
model Fog::CDN::AWS::Distribution
attribute :marker, :aliases => 'Marker'
attribute :max_items, :aliases => 'MaxItems'
attribute :is_truncated, :aliases => 'IsTruncated'
def get_distribution(dist_id)
service.get_distribution(dist_id)
end
def list_distributions(options = {})
service.get_distribution_list(options)
end
alias :each_distribution_this_page :each
end
end
end
end

View file

@ -0,0 +1,47 @@
require 'fog/core/collection'
module Fog
module CDN
class AWS
module DistributionsHelper
def all(options = {})
merge_attributes(options)
data = list_distributions(options).body
merge_attributes('IsTruncated' => data['IsTruncated'], 'Marker' => data['Marker'], 'MaxItems' => data['MaxItems'])
if summary = data['DistributionSummary']
load(summary.map { |a| { 'DistributionConfig' => a } })
else
load((data['StreamingDistributionSummary'] || {}).map { |a| { 'StreamingDistributionConfig' => a }})
end
end
def get(dist_id)
response = get_distribution(dist_id)
data = response.body.merge({'ETag' => response.headers['ETag']})
new(data)
rescue Excon::Errors::NotFound
nil
end
def each
if !block_given?
self
else
subset = dup.all
subset.each_distribution_this_page {|f| yield f}
while subset.is_truncated
subset = subset.all('Marker' => subset.marker, 'MaxItems' => 1000)
subset.each_distribution_this_page {|f| yield f}
end
self
end
end
end
end
end
end

View file

@ -0,0 +1,64 @@
require 'fog/core/model'
module Fog
module CDN
class AWS
class Invalidation < Fog::Model
identity :id, :aliases => 'Id'
attribute :status, :aliases => 'Status'
attribute :create_time, :aliases => 'CreateTime'
attribute :caller_reference, :aliases => 'CallerReference'
attribute :paths, :aliases => 'Paths'
def initialize(new_attributes={})
new_attributes[:caller_reference] ||= Time.now.utc.to_i.to_s
super(invalidation_to_attributes(new_attributes))
end
def distribution
@distribution
end
def ready?
requires :id, :status
status == 'Completed'
end
def save
requires :paths, :caller_reference
raise "Submitted invalidation cannot be submitted again" if persisted?
response = service.post_invalidation(distribution.identity, paths, caller_reference)
merge_attributes(invalidation_to_attributes(response.body))
true
end
def destroy
# invalidations can't be removed, but tests are requiring they do :)
true
end
private
def distribution=(dist)
@distribution = dist
end
def invalidation_to_attributes(new_attributes={})
invalidation_batch = new_attributes.delete('InvalidationBatch') || {}
if invalidation_batch['Path']
new_attributes[:paths] = invalidation_batch['Path']
end
if invalidation_batch['CallerReference']
new_attributes[:caller_reference] = invalidation_batch['CallerReference']
end
new_attributes
end
end
end
end
end

View file

@ -0,0 +1,54 @@
require 'fog/core/collection'
require 'fog/aws/models/cdn/invalidation'
module Fog
module CDN
class AWS
class Invalidations < Fog::Collection
attribute :is_truncated, :aliases => ['IsTruncated']
attribute :max_items, :aliases => ['MaxItems']
attribute :next_marker, :aliases => ['NextMarker']
attribute :marker, :aliases => ['Marker']
attribute :distribution
model Fog::CDN::AWS::Invalidation
def all(options = {})
requires :distribution
options[:max_items] ||= max_items
options.delete_if {|key, value| value.nil?}
data = service.get_invalidation_list(distribution.identity, options).body
merge_attributes(data.reject {|key, value| !['IsTruncated', 'MaxItems', 'NextMarker', 'Marker'].include?(key)})
load(data['InvalidationSummary'])
end
def get(invalidation_id)
requires :distribution
data = service.get_invalidation(distribution.identity, invalidation_id).body
if data
invalidation = new(data)
else
nil
end
rescue Excon::Errors::NotFound
nil
end
def new(attributes = {})
requires :distribution
super({ :distribution => distribution }.merge!(attributes))
end
end
end
end
end

View file

@ -0,0 +1,77 @@
require 'fog/core/model'
require 'fog/aws/models/cdn/invalidations'
require 'fog/aws/models/cdn/distribution_helper'
module Fog
module CDN
class AWS
class StreamingDistribution < Fog::Model
include Fog::CDN::AWS::DistributionHelper
identity :id, :aliases => 'Id'
attribute :caller_reference, :aliases => 'CallerReference'
attribute :last_modified_time, :aliases => 'LastModifiedTime'
attribute :status, :aliases => 'Status'
attribute :s3_origin, :aliases => 'S3Origin'
attribute :cname, :aliases => 'CNAME'
attribute :comment, :aliases => 'Comment'
attribute :enabled, :aliases => 'Enabled'
attribute :logging, :aliases => 'Logging'
attribute :domain, :aliases => 'DomainName'
attribute :etag, :aliases => ['Etag', 'ETag']
# items part of DistributionConfig
CONFIG = [ :caller_reference, :cname, :comment, :enabled, :logging ]
def initialize(new_attributes = {})
super(distribution_config_to_attributes(new_attributes))
end
def save
requires_one :s3_origin
options = attributes_to_options
response = identity ? put_distribution_config(identity, etag, options) : post_distribution(options)
etag = response.headers['ETag']
merge_attributes(response.body)
true
end
private
def delete_distribution(identity, etag)
service.delete_streaming_distribution(identity, etag)
end
def put_distribution_config(identity, etag, options)
service.put_streaming_distribution_config(identity, etag, options)
end
def post_distribution(options = {})
service.post_streaming_distribution(options)
end
def attributes_to_options
options = {
'CallerReference' => caller_reference,
'S3Origin' => s3_origin,
'CNAME' => cname,
'Comment' => comment,
'Enabled' => enabled,
'Logging' => logging,
}
options.reject! { |k,v| v.nil? }
options.reject! { |k,v| v.respond_to?(:empty?) && v.empty? }
options
end
def distribution_config_to_attributes(new_attributes = {})
new_attributes.merge(new_attributes.delete('StreamingDistributionConfig') || {})
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show more