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:
commit
03bf99d9d3
2787 changed files with 88180 additions and 24691 deletions
|
@ -1,3 +1,3 @@
|
|||
README.rdoc
|
||||
README.md
|
||||
lib/**/*.rb
|
||||
bin/*
|
||||
|
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -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
82
.irbrc
Normal 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
21
.travis.yml
Normal 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
156
README.md
Normal 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.
|
154
README.rdoc
154
README.rdoc
|
@ -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
39
RELEASE.md
Normal 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
373
Rakefile
|
@ -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
|
||||
|
|
8
bin/fog
8
bin/fog
|
@ -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
|
||||
|
|
2242
changelog.txt
2242
changelog.txt
File diff suppressed because it is too large
Load diff
|
@ -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
|
|
@ -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>
|
|
@ -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.
|
|
@ -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)
|
|
@ -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/)
|
|
@ -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!
|
|
@ -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
|
|
@ -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!
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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 don’t understand all of the points described here, you probably
|
||||
have a nasty security vulnerability. ~ simon willison
|
||||
-->
|
||||
|
||||
</cross-domain-policy>
|
|
@ -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; } */
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
* {
|
||||
float: none;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
body { font-size: 80%; }
|
|
@ -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; }
|
||||
}
|
||||
|
3
docs/public/images/.gitignore
vendored
3
docs/public/images/.gitignore
vendored
|
@ -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
6240
docs/public/js/libs/jquery-1.4.2.js
vendored
6240
docs/public/js/libs/jquery-1.4.2.js
vendored
File diff suppressed because it is too large
Load diff
154
docs/public/js/libs/jquery-1.4.2.min.js
vendored
154
docs/public/js/libs/jquery-1.4.2.min.js
vendored
|
@ -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);
|
30
docs/public/js/libs/modernizr-1.6.min.js
vendored
30
docs/public/js/libs/modernizr-1.6.min.js
vendored
|
@ -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);
|
3
docs/public/js/mylibs/.gitignore
vendored
3
docs/public/js/mylibs/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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) }
|
||||
|
|
@ -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;}
|
39
docs/public/js/profiling/yahoo-profiling.min.js
vendored
39
docs/public/js/profiling/yahoo-profiling.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,26 +0,0 @@
|
|||
/* Author:
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
21
docs/storage/aws.markdown
Normal 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'
|
||||
)
|
|
@ -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 => 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!
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
16
fog.gemspec
16
fog.gemspec
|
@ -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")
|
||||
|
|
|
@ -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
11
lib/fog/atmos.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require 'fog/core'
|
||||
|
||||
module Fog
|
||||
module Atmos
|
||||
|
||||
extend Fog::Provider
|
||||
|
||||
service(:storage, 'atmos/storage', 'Storage')
|
||||
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
module Fog
|
||||
module Storage
|
||||
class Ninefold
|
||||
class Atmos
|
||||
class Real
|
||||
|
||||
def delete_namespace(namespace = '', options = {})
|
|
@ -1,6 +1,6 @@
|
|||
module Fog
|
||||
module Storage
|
||||
class Ninefold
|
||||
class Atmos
|
||||
class Real
|
||||
|
||||
def get_namespace(namespace = '', options = {})
|
20
lib/fog/atmos/requests/storage/head_namespace.rb
Normal file
20
lib/fog/atmos/requests/storage/head_namespace.rb
Normal 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
|
|
@ -1,6 +1,6 @@
|
|||
module Fog
|
||||
module Storage
|
||||
class Ninefold
|
||||
class Atmos
|
||||
class Real
|
||||
|
||||
def post_namespace(namespace = '', options = {})
|
|
@ -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
186
lib/fog/atmos/storage.rb
Normal 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
|
122
lib/fog/aws.rb
122
lib/fog/aws.rb
|
@ -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
|
||||
|
|
|
@ -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
152
lib/fog/aws/beanstalk.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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' },
|
||||
|
|
|
@ -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
|
||||
|
|
64
lib/fog/aws/credential_fetcher.rb
Normal file
64
lib/fog/aws/credential_fetcher.rb
Normal 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
|
||||
|
|
@ -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
142
lib/fog/aws/dynamodb.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
466
lib/fog/aws/elb/policy_types.rb
Normal file
466
lib/fog/aws/elb/policy_types.rb
Normal 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
134
lib/fog/aws/emr.rb
Normal 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
172
lib/fog/aws/glacier.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
33
lib/fog/aws/models/auto_scaling/policies.rb
Normal file
33
lib/fog/aws/models/auto_scaling/policies.rb
Normal 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
|
46
lib/fog/aws/models/auto_scaling/policy.rb
Normal file
46
lib/fog/aws/models/auto_scaling/policy.rb
Normal 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
|
62
lib/fog/aws/models/beanstalk/application.rb
Normal file
62
lib/fog/aws/models/beanstalk/application.rb
Normal 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
|
25
lib/fog/aws/models/beanstalk/applications.rb
Normal file
25
lib/fog/aws/models/beanstalk/applications.rb
Normal 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
|
145
lib/fog/aws/models/beanstalk/environment.rb
Normal file
145
lib/fog/aws/models/beanstalk/environment.rb
Normal 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
|
29
lib/fog/aws/models/beanstalk/environments.rb
Normal file
29
lib/fog/aws/models/beanstalk/environments.rb
Normal 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
|
20
lib/fog/aws/models/beanstalk/event.rb
Normal file
20
lib/fog/aws/models/beanstalk/event.rb
Normal 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
|
19
lib/fog/aws/models/beanstalk/events.rb
Normal file
19
lib/fog/aws/models/beanstalk/events.rb
Normal 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
|
78
lib/fog/aws/models/beanstalk/template.rb
Normal file
78
lib/fog/aws/models/beanstalk/template.rb
Normal 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
|
70
lib/fog/aws/models/beanstalk/templates.rb
Normal file
70
lib/fog/aws/models/beanstalk/templates.rb
Normal 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
|
79
lib/fog/aws/models/beanstalk/version.rb
Normal file
79
lib/fog/aws/models/beanstalk/version.rb
Normal 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
|
31
lib/fog/aws/models/beanstalk/versions.rb
Normal file
31
lib/fog/aws/models/beanstalk/versions.rb
Normal 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
|
93
lib/fog/aws/models/cdn/distribution.rb
Normal file
93
lib/fog/aws/models/cdn/distribution.rb
Normal 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
|
64
lib/fog/aws/models/cdn/distribution_helper.rb
Normal file
64
lib/fog/aws/models/cdn/distribution_helper.rb
Normal 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
|
32
lib/fog/aws/models/cdn/distributions.rb
Normal file
32
lib/fog/aws/models/cdn/distributions.rb
Normal 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
|
47
lib/fog/aws/models/cdn/distributions_helper.rb
Normal file
47
lib/fog/aws/models/cdn/distributions_helper.rb
Normal 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
|
64
lib/fog/aws/models/cdn/invalidation.rb
Normal file
64
lib/fog/aws/models/cdn/invalidation.rb
Normal 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
|
54
lib/fog/aws/models/cdn/invalidations.rb
Normal file
54
lib/fog/aws/models/cdn/invalidations.rb
Normal 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
|
77
lib/fog/aws/models/cdn/streaming_distribution.rb
Normal file
77
lib/fog/aws/models/cdn/streaming_distribution.rb
Normal 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
Loading…
Add table
Reference in a new issue